Imports System.Collections
Imports System.Collections.Specialized
Imports System.Drawing
Imports System.Drawing.Drawing2D
Imports System.Windows
Imports System.Net
Imports System.Resources
Imports System.Reflection
Imports Yvdh
Imports CIPF.Annotations.Util
Imports CIPF.Annotations
Imports System.ComponentModel

Namespace Annotations
  ''' <summary>
  ''' The event arguments passed when a property of the <b>AnnotatedImage</b> changes. The more important 
  ''' properties will have their own event.
  ''' </summary>
  ''' <remarks>
  ''' </remarks>
  Public Class AnnotatedImageChangedEventArgs
    Inherits System.EventArgs

    ''' <summary>
    ''' The possible types of AnnotatedImageChangedEventArgs event
    ''' </summary>
    ''' <remarks>These mirror most of the <b>AnnotatedImage</b> properties. Some of the 
    ''' more important properties have their own events though. This is also true of the 
    ''' collection events.
    ''' </remarks>
    Enum PropertyType
      'Bitmap has its own event handler
      'BitmapData
      'SelectedItem  has its own event handler
      PhysicalZoom
      DisplayZoom
      DisplayOffset
      Resolution
      DrawMasks
      DrawAnnotations
      EnableContextMenus
      EnablePropertyDialogs
      PixelAveragingWindowSize
      BitmapUri
      AnnotationUri
      AnnotationsManagerVisible
      'CreatingAnnotation  has its own event handler
    End Enum

    Private _type As PropertyType
    ''' <summary>
    ''' The type of event
    ''' </summary>
    ''' <value></value>
    ''' <remarks>
    ''' </remarks>
    Public Property Type() As PropertyType
      Get
        Return _type
      End Get
      Set(ByVal value As PropertyType)
        _type = value
      End Set
    End Property

    Public Sub New(ByVal propertyType As PropertyType)
      _type = propertyType
    End Sub

    Public Sub New()
    End Sub
  End Class

  ''' <summary>
  ''' The main control that allows the display of an image with overlaid annotations consisting of polygons and masks.
  ''' </summary>
  ''' <remarks>
  ''' </remarks>
  Public Class AnnotatedImage
    Inherits System.Windows.Forms.Control
    Implements ILockable, ISelectable

#Region "Variables"
    'Bitmap variables, mirrored by a property
    Private _bitmap As Bitmap
    Private _averagingWindowSize As Integer
    Private _dirty, _updating As Boolean, _busy As Boolean = False
    Private _bitmapSaved As Boolean = True, _annotationsSaved As Boolean = True
    Private currentAnnotationsVisibility, _constantLineWidthAndFont As Boolean
    Private _displayZoom As Single = 1.0, _displayOffset As New Point(0, 0), _bitmapURI, _annotationsURI As Uri
    Private _URISessionID As String, _selectedItem As ISelectable
    Private _enableTooltips, _enableAutoFocus As Boolean

    'Optimization: Create a 32bppPARGB bitmap for faster display, but this requires more memory
    Private _optimizeBitmapDisplay As Boolean, _optimizedBitmap As Bitmap

    'Visibility and UI
    Private _drawMasks, _drawAnnotations, _enableContextMenus, _enablePropertyDialogs As Boolean

    Private WithEvents _masks As New Masks
    Private WithEvents _annotations As New Annotations

    'Private variables for interactive creation and editing of annotations. Currently these 
    'annotations are just polygons.
    Private _creatingAnnotation As Boolean, _annotation As Annotation
    Private lastMousePos As New Point(0, 0), moving As Boolean = False

    'Context sensitive menus on the canvas, which are shared with the annotation and mask manager.
    'Menus regarding masks cannot be activated on the canvas and are private to the annotation and mask manager
    Friend annotationMenu, annotationsMenu, creatingAnnotationMenu, annotationContainerMenu, MaskMenu, MasksMenu As ContextMenu

    'Custom cursors
    Private cursorSelectAnnotation, cursorSelectAnnotationContainer, cursorSelectPoint, cursorDraw, cursorCross, cursorMove As Cursor

    'Misc
    Private _screenResolution As Integer = 96
#End Region

#Region "Constants and enumerations"

    Public Enum AnnotationCreationEventArgs
      Start = 0
      Ended
      Canceled
    End Enum

    Enum ContextMenus
      Annotation = 1
      AnnotationNew = 2
      AnnotationContainer = 3
      Masks = 4
      Mask = 5
    End Enum

    Friend WithEvents ttHint As System.Windows.Forms.ToolTip
    Private Const CreatedAnnotationMinimalPointDistance As Integer = 2
#End Region

#Region "Public Events"
    ''' <summary>
    ''' Raised when the selected property is changed
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <remarks></remarks>
    Public Event SelectedChanged(ByVal sender As ISelectable) Implements ISelectable.SelectedChanged

    ''' <summary>
    ''' Generic property changed event handler. Important properties have their own event handler, 
    ''' but coalescing the minor ones here avoids having a proliferation of event handlers.
    ''' </summary>
    ''' <remarks>Not all properties have an event, but most do.
    ''' </remarks>
    Public Event Changed(ByVal sender As Object, ByVal e As AnnotatedImageChangedEventArgs)

    ''' <summary>
    ''' This event is raised when the user starts/cancels/ends the creation of an annotation interactively.
    ''' </summary>
    ''' <remarks>
    ''' </remarks>
    Public Event AnnotationCreation(ByVal sender As Object, ByVal e As AnnotationCreationEventArgs)

    '''' <summary>
    '''' This event is raised when an annotation, annotation container or mask is selected.
    '''' </summary>
    '''' <remarks>Check the type of sender to determine which object is selected
    '''' </remarks>
    Public Event ItemSelected(ByVal sender As ISelectable)

    ''' <summary>
    ''' This event is raised when the collection of annotation containers is changed.
    ''' </summary>
    ''' <remarks>
    ''' </remarks>
    Public Event AnnotationsChanged(ByVal sender As Object, ByVal e As CollectionChangedEventArgs)

    ''' <summary>
    ''' This event is raised when an annotation container are modified.
    ''' </summary>
    ''' <remarks>
    ''' </remarks>
    Public Event AnnotationContainerChanged(ByVal sender As Object, ByVal e As CollectionChangedEventArgs)

    ''' <summary>
    ''' This event is raised when an annotation is modified.
    ''' </summary>
    ''' <remarks>
    ''' </remarks>
    Public Event AnnotationChanged(ByVal sender As Object, ByVal e As CollectionChangedEventArgs)

    '''' <summary>
    '''' This event is raised when the collection of points in an annotation changes.
    '''' </summary>
    '''' <remarks>
    '''' </remarks>
    'Public Event AnnotationPointsChanged(ByVal sender As Object, ByVal e As CollectionChangedEventArgs)

    '''' <summary>
    '''' This event is raised when the collection of annotations of an annotation container is changed.
    '''' </summary>
    '''' <remarks>
    '''' </remarks>
    'Public Event AnnotationSetChanged(ByVal sender As Object, ByVal e As CollectionChangedEventArgs)

    ''' <summary>
    ''' This event is raised when the bitmap of the annotatedImage is set.
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <remarks>
    ''' </remarks>
    Public Event BitmapChanged(ByVal sender As Object)

    ''' <summary>
    ''' This event is raised when the data of the annotatedImage bitmap is changed
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <remarks>The control cannot monitor for this event itself (it would generate way to much false positives), 
    ''' and thus the user must raise it manually using <b>RaiseBitmapDataChangedEvent</b> when modifying the 
    ''' data of the bitmap, e.g. when doing image processing.
    ''' </remarks>
    Public Event BitmapDataChanged(ByVal sender As Object) 'Occurs when we change the data of a bitmap...

    ''' <summary>
    ''' This event is raised when the collection of image masks is changed.
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    ''' <remarks>
    ''' </remarks>
    Public Event MasksChanged(ByVal sender As Object, ByVal e As CollectionChangedEventArgs) 'Occurs when adding or deleting a mask. User is responsible for refresh when changing the mask itself ...

    ''' <summary>
    ''' This event is raised after an update. This happens when the user sets the <b>Updating</b> to true to perform
    ''' multiple operations on the annotations and bitmap to avoid unnecessary costly redraws, and then sets it to false again when done.
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <remarks>
    ''' </remarks>
    Public Event Updated(ByVal sender As Object) 'Occurs after an update


    ''' <summary>
    ''' Occurs when the busy property changes. Use this event to set the cursor for the parent form, 
    ''' and to block user interaction when performing lengthy computations.
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <remarks></remarks>
    Public Event BusyChanged(ByVal sender As Object)
#End Region

#Region "Component Designer generated code "

    Public Sub New()
      MyBase.New()

      ' This call is required by the Component Designer.
      InitializeComponent()

      'Double buffering and smooth drawing
      SetStyle(ControlStyles.UserPaint, True)
      SetStyle(ControlStyles.AllPaintingInWmPaint, True)
      SetStyle(ControlStyles.DoubleBuffer, True)

      'Default properties that can be changed. Leave those in the declarations alone
      _updating = False
      _drawMasks = True
      _drawAnnotations = True
      _enableContextMenus = True
      _enablePropertyDialogs = True
      _optimizeBitmapDisplay = True
      _constantLineWidthAndFont = True
      _enableTooltips = True
      _enableAutoFocus = True
      _screenResolution = 96

      'Create context menus
      CreateContextMenus()

      'Load custom cursors. This will not work when used as a smartclient!!!!!!!
      Dim thisAssembly As Reflection.Assembly = Reflection.Assembly.GetExecutingAssembly()

      Me.cursorSelectAnnotation = New Cursor(thisAssembly.GetManifestResourceStream("CIPF.SelectAnnotation.cur"))
      Me.cursorSelectAnnotationContainer = New Cursor(thisAssembly.GetManifestResourceStream("CIPF.SelectAnnotationContainer.cur"))
      Me.cursorSelectPoint = New Cursor(thisAssembly.GetManifestResourceStream("CIPF.SelectPoint.cur"))
      Me.cursorDraw = New Cursor(thisAssembly.GetManifestResourceStream("CIPF.Draw.cur"))
      'Me.cursorCross = New Cursor(thisAssembly.GetManifestResourceStream("CIPF.Cross.cur"))
      Me.cursorCross = Cursors.Cross
      Me.cursorMove = New Cursor(thisAssembly.GetManifestResourceStream("CIPF.Move.cur"))
    End Sub

    'Control overrides dispose to clean up the component list.
    Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
      If disposing Then
        If Not (components Is Nothing) Then
          components.Dispose()
        End If
      End If
      MyBase.Dispose(disposing)
    End Sub

    Protected Overrides Sub Finalize()
      Me._bitmap = Nothing
    End Sub

    'Required by the Control Designer
    Private components As System.ComponentModel.IContainer

    ' NOTE: The following procedure is required by the Component Designer
    ' It can be modified using the Component Designer.  Do not modify it
    ' using the code editor.
    <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
      Me.components = New System.ComponentModel.Container
      Me.ttHint = New System.Windows.Forms.ToolTip(Me.components)
      Me.SuspendLayout()
      Me.ResumeLayout(False)

    End Sub
#End Region

#Region "Properties"
    Dim _selected As Boolean = False
    ''' <summary>
    ''' True if selected.
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks>
    ''' </remarks>
    Public Property Selected() As Boolean Implements ISelectable.Selected
      Get
        Return Me._selected
      End Get
      Set(ByVal value As Boolean)
        Me._selected = value
        RaiseEvent SelectedChanged(Me)
      End Set
    End Property

    <DefaultValue(96)> _
    Public Property ScreenResolution() As Integer
      Get
        Return Me._screenResolution
      End Get
      Set(ByVal value As Integer)
        Me._screenResolution = value
      End Set
    End Property

    ''' <summary>
    ''' Set the bitmap. Use this after having modified the bitmap data directly, it will trigger a 
    ''' refresh of the cached display bitmap and raise the BitmapData changed event. It will also
    ''' trigger a redraw if Updating is false, and set the BitmapSaved property to false.
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub SetBitmap()
      _bitmapSaved = False

      Debug.WriteLine("Manually raising BitmapDataChanged event", "RaiseBitmapDataChangedEvent")
      CreateOptimizedBitmap()
      Me.Draw()
      RaiseEvent BitmapDataChanged(Me)
    End Sub

    ''' <summary>
    ''' Set the bitmap. Use this to replace the bitmap without clearing the masks and annotation containers.
    ''' It will trigger a refresh of the cached display bitmap and raise the BitmapChanged event. 
    ''' It will also set the BitmapSaved property to false. The displayzoom is not recomputed and thus not modified.
    ''' Finally, it will trigger the Updated event and redraw if appropriate
    ''' </summary>
    ''' <param name="image"></param>
    ''' <remarks></remarks>
    Public Sub SetBitmap(ByVal image As Bitmap)
      SetBitmap(image, False)
    End Sub

    ''' <summary>
    ''' Set the bitmap. When clear is true this will also clear the masks and annotations containers.
    ''' It will trigger a refresh of the cached display bitmap and raise the BitmapChanged event. 
    ''' It will also set the BitmapSaved property to false.The displayzoom is only recomputed if clear is true.
    ''' Finally, it will trigger the Updated event and redraw if appropriate
    ''' </summary>
    ''' <param name="image"></param>
    ''' <param name="clear"></param>
    ''' <remarks></remarks>
    Public Sub SetBitmap(ByVal image As Bitmap, ByVal clear As Boolean)
      Me.Updating = True
      Me._displayOffset.X = 0
      Me._displayOffset.Y = 0

      If clear Then
        Me._annotations.Clear()
        Me._masks.Clear()
        Me._bitmap = image
        Me._displayZoom = ComputeInitialDisplayZoom()
        CreateOptimizedBitmap()
        SizeControl()
      Else
        _bitmap = image
        CreateOptimizedBitmap()
        SizeControl()
      End If

      Me._bitmapSaved = False
      RaiseEvent BitmapChanged(image)
      Me.Updating = False
    End Sub

    ''' <summary>
    ''' The bitmap of the annotatedImage.
    ''' </summary>
    ''' <value></value>
    ''' <remarks>Return a reference to the bitmap, so if the user changes the data of this bitmap he must call
    ''' the RaiseBitmapDataChangedEvent manually.
    ''' </remarks>
    Public ReadOnly Property Bitmap() As Bitmap
      Get
        Return _bitmap
      End Get
    End Property

    ''' <summary>
    ''' The mask collection
    ''' </summary>
    ''' <value></value>
    ''' <remarks>
    ''' </remarks>
    Public ReadOnly Property Masks() As Masks
      Get
        Return _masks
      End Get
    End Property

    ''' <summary>
    ''' The annotations.
    ''' </summary>
    ''' <value></value>
    ''' <remarks>
    ''' </remarks>
    Public ReadOnly Property Annotations() As Annotations
      Get
        Return _annotations
      End Get
    End Property

    ''' <summary>
    ''' Whether we are busy with some background computations.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks>Use the BusyChanged event to handle cursor changes and UI disable/enable in the parent form. 
    ''' Setting Busy to true from nested functions causes no problems, it will only be set back to false after the 
    ''' appropriate number of calls.</remarks>
    <DefaultValue(False), Browsable(False)> _
    Public Property Busy() As Boolean
      Get
        Return _busy
      End Get
      Set(ByVal value As Boolean)
        Static nesting As Integer = 0
        If value Then
          If _busy Then
            nesting += 1
          Else
            _busy = True
            FormUtil.Busy(Me, True)
            RaiseEvent BusyChanged(Me)
          End If
        Else
          If _busy Then
            If nesting = 0 Then
              _busy = False
              FormUtil.Busy(Me, False)
              RaiseEvent BusyChanged(Me)
            Else
              nesting -= 1
            End If
          End If
        End If
        Debug.WriteLine("Called with " & value.ToString & ", set to " & _busy.ToString & "(nesting = " & nesting & ")", "AnnotatedImage.Busy")
      End Set
    End Property

    ''' <summary>
    ''' Set or get the current image tooltip text.
    ''' </summary>
    ''' <value></value>
    ''' <remarks></remarks>
    <DefaultValue("")> _
    Public Property TooltipText() As String
      Get
        Return Me.ttHint.GetToolTip(Me)
      End Get
      Set(ByVal value As String)
        If Me._enableTooltips Then Me.ttHint.SetToolTip(Me, value)
      End Set
    End Property

    ''' <summary>
    ''' Determines whether tooltip help is shown on the image.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    <DefaultValue(True)> _
    Public Property EnableTooltips() As Boolean
      Get
        Return Me._enableTooltips
      End Get
      Set(ByVal value As Boolean)
        Me._enableTooltips = value
        If Not value Then
          Me.ttHint.RemoveAll()
        End If
      End Set
    End Property

    ''' <summary>
    ''' If true (default), control automatically gets focus if the mouse comes over it. 
    ''' Otherwise the user needs to click it first to have it process key and mouse events.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    <DefaultValue(True)> _
    Public Property EnableAutoFocus() As Boolean
      Get
        Return Me._enableAutoFocus
      End Get
      Set(ByVal value As Boolean)
        Me._enableAutoFocus = value
      End Set
    End Property

    Private _highQualityZoom As Boolean = True
    ''' <summary>
    ''' Enabled or disable highquality (interpolated) zoom.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    Public Property highQualityZoom() As Boolean
      Get
        Return Me._highQualityZoom
      End Get
      Set(ByVal value As Boolean)
        Me._highQualityZoom = value
      End Set
    End Property

    Private _enableZoom As Boolean = True
    ''' <summary>
    ''' Enabled or disable zooming using keyboard or mouse.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks>It is always possible to set the displayzoom property, regardless of this setting</remarks>
    Public Property EnableZoom() As Boolean
      Get
        Return Me._enableZoom
      End Get
      Set(ByVal value As Boolean)
        Me._enableZoom = value
      End Set
    End Property

    Private _enableAutoNavigator As Boolean = True
    ''' <summary>
    ''' Enabled or disable automatic popup of the image navigator when the displayed image is bigger than the screen
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property EnableAutoNavigator() As Boolean
      Get
        Return Me._enableAutoNavigator
      End Get
      Set(ByVal value As Boolean)
        Me._enableAutoNavigator = value
      End Set
    End Property

    Private _enableAutomagnifier As Boolean = False
    ''' <summary>
    ''' Enabled or disable automatic popup of the image magnifier when the display zoom is smaller than 1.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property EnableAutoMagnifier() As Boolean
      Get
        Return Me._enableAutomagnifier
      End Get
      Set(ByVal value As Boolean)
        Me._enableAutomagnifier = value
      End Set
    End Property

    Private _maximumDisplaySize As New Size(0, 0)
    ''' <summary>
    ''' The size of the displayed image can never exceed this size. This in effect limits the display zoom.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks>No limit if size = 0,0. 
    ''' This is different from the MaximumSize property which limits the size of the control 
    ''' (a control may display an image larger than its own size using e.g. scrollbars)
    ''' For the minimum display size one can use the MinimumSize property, they have the same meaning.</remarks>
    Public Property MaximumDisplaySize() As Size
      Get
        Return Me._maximumDisplaySize
      End Get
      Set(ByVal value As Size)
        If Me.MaximumSize.Height <= value.Height And Me.MaximumSize.Width <= value.Width Then
          Me._maximumDisplaySize = value
        End If
      End Set
    End Property

    Private _locked As Boolean = False
    ''' <summary>
    ''' No UI changes (add, delete, edit, selection) of any annotation possible if true.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks>Mouse events are still raised, as opposed to using 'Enabled'.</remarks>
    Public Property Locked() As Boolean Implements ILockable.Locked
      Get
        Return (Me._locked)
      End Get
      Set(ByVal value As Boolean)
        Me._locked = value
      End Set
    End Property

    ''' <summary>
    ''' True if the bitmap was changed since last save and thus needs to be saved
    ''' </summary>
    ''' <value></value>
    ''' <remarks>
    ''' </remarks>
    <DefaultValue(True), Browsable(False)> _
    Public ReadOnly Property BitmapSaved() As Boolean
      Get
        Return _bitmapSaved
      End Get
    End Property

    ''' <summary>
    ''' True if the annotations have changed since last save and thus need to be saved
    ''' </summary>
    ''' <value></value>
    ''' <remarks>
    ''' </remarks>
    <DefaultValue(True), Browsable(False)> _
   Public ReadOnly Property AnnotationsSaved() As Boolean
      Get
        Return _annotationsSaved
      End Get
    End Property

    ''' <summary>
    ''' When true a copy of the main bitmap is made in the native GDI+ format, speeding up display at the expense of using more memory.
    ''' </summary>
    ''' <value></value>
    ''' <remarks>
    ''' </remarks>
    <DefaultValue(True)> _
    Public Property OptimizeBitmapDisplay() As Boolean
      Get
        Return _optimizeBitmapDisplay
      End Get
      Set(ByVal Value As Boolean)
        _optimizeBitmapDisplay = Value
        CreateOptimizedBitmap()
      End Set
    End Property

    ''' <summary>
    ''' The selected item, if any! Can be a mask, an annotation or an annotationcontainer
    ''' </summary>
    ''' <value></value>
    ''' <remarks>
    ''' </remarks>
    <Browsable(False)> _
    Public ReadOnly Property SelectedItem() As ISelectable
      Get
        'Return the selected item if any
        Return _selectedItem
      End Get
    End Property

    ''' <summary>
    ''' The Uri of the loaded annotations.
    ''' </summary>
    ''' <value></value>
    ''' <remarks>
    ''' </remarks>
    <DefaultValue(""), Browsable(False)> _
    Public Property AnnotationsURI() As Uri
      Set(ByVal Value As Uri)
        'Debug.WriteLine(Value.ToString, "AnnotatedImage.AnnotationsURI")
        _annotationsURI = Value
        RaiseEvent Changed(Me, New AnnotatedImageChangedEventArgs(AnnotatedImageChangedEventArgs.PropertyType.AnnotationUri))
      End Set
      Get
        Return _annotationsURI
      End Get
    End Property

    ''' <summary>
    ''' Set this to true when making extensive changes to the bitmap, masks or polygons.
    ''' By checking this property in the event handlers it is then possible to ignore multiple events 
    ''' from the mask and annotation collections, and only take appropriate steps in the Updated event which is
    ''' triggered after the changes when the Updating property is set back to false. Setting Updating to false also calls a 
    ''' redraw  as all redraws are also blocked when Updating is true.
    ''' </summary>
    ''' <value></value>
    ''' <remarks>Do not forget to set this to false after the changes have been made, otherwise drawing will not resume! 
    ''' In order to avoid problems with multiple nested functions setting 
    ''' this to true we keep track of this nesting before resetting Updating to false!
    ''' </remarks>
    <DefaultValue(False), Browsable(False)> _
    Public Property Updating() As Boolean
      Get
        Return _updating
      End Get
      Set(ByVal Value As Boolean)
        Static nesting As Integer = 0
        'Do this seperately if required! Me.Busy = Value
        If Value Then
          If _updating Then
            nesting += 1
          Else
            _updating = True
          End If
        Else
          If _updating Then
            If nesting = 0 Then
              _updating = False
              Me.Draw()
              RaiseEvent Updated(Me)
            Else
              nesting -= 1
            End If
          End If
        End If
        Debug.WriteLine("Called with " & Value.ToString & ", set to " & _updating.ToString & " (nesting = " & nesting & ")", "AnnotatedImage.Updating")
      End Set
    End Property

    ''' <summary>
    ''' Enable/disable the context sensitive menus on the image and the annotations.
    ''' </summary>
    ''' <value></value>
    ''' <remarks>The key combinations will still work (Enter, Esc, Ctrl-Z, Del)!
    ''' </remarks>
    <DefaultValue(True)> _
    Public Property EnableContextMenus() As Boolean
      Get
        Return _enableContextMenus
      End Get
      Set(ByVal Value As Boolean)
        _enableContextMenus = Value
        RaiseEvent Changed(Me, New AnnotatedImageChangedEventArgs(AnnotatedImageChangedEventArgs.PropertyType.EnableContextMenus))
      End Set
    End Property

    ''' <summary>
    ''' Enable/disable the property dialogs on the annotations and annotationcontainers
    ''' </summary>
    ''' <value></value>
    ''' <remarks>
    ''' </remarks>
    <DefaultValue(True)> _
    Public Property EnablePropertyDialogs() As Boolean
      Get
        Return _enablePropertyDialogs
      End Get
      Set(ByVal Value As Boolean)
        _enablePropertyDialogs = Value
        RaiseEvent Changed(Me, New AnnotatedImageChangedEventArgs(AnnotatedImageChangedEventArgs.PropertyType.EnablePropertyDialogs))
      End Set
    End Property

    ''' <summary>
    ''' The Uri of the loaded bitmap
    ''' </summary>
    ''' <value></value>
    ''' <remarks>
    ''' </remarks>
    <DefaultValue(""), Browsable(False)> _
    Public Property BitmapURI() As Uri
      Set(ByVal Value As Uri)
        _bitmapURI = Value
        RaiseEvent Changed(Me, New AnnotatedImageChangedEventArgs(AnnotatedImageChangedEventArgs.PropertyType.BitmapUri))
      End Set
      Get
        Return _bitmapURI
      End Get
    End Property

    ''' <summary>
    ''' The session ID, if any, that goes with the URI's. Allows posting back to the same session.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    <DefaultValue(""), Browsable(False)> _
    Public Property URISessionID() As String
      Set(ByVal Value As String)
        _URISessionID = Value
      End Set
      Get
        Return _URISessionID
      End Get
    End Property

    ''' <summary>
    ''' The display offset controls the position of the image inside the control, especially when it is 
    ''' bigger than that control. This value is in client coordinates (pixels).
    ''' </summary>
    ''' <value></value>
    ''' <remarks>
    ''' </remarks>
    <Browsable(False)> _
    Public Property DisplayOffset() As Point
      Get
        Return _displayOffset
      End Get
      Set(ByVal Value As Point)
        Debug.WriteLine("Set display offset at " & Value.ToString(), "AnnotatedImage.DisplayOffset")
        If Value.X < 0 Then Value.X = 0
        If Value.Y < 0 Then Value.Y = 0
        Dim iDeltaX As Integer = CInt((DisplaySize.Width - Me.Width) / Me.DisplayZoom)
        Dim iDeltaY As Integer = CInt((DisplaySize.Height - Me.Height) / Me.DisplayZoom)

        If Value.X > iDeltaX Then Value.X = iDeltaX
        If Value.Y > iDeltaY Then Value.Y = iDeltaY
        _displayOffset = Value

        'This is exceptionally necessary.
        Me.Draw()
        RaiseEvent Changed(Me, New AnnotatedImageChangedEventArgs(AnnotatedImageChangedEventArgs.PropertyType.DisplayOffset))
      End Set
    End Property

    ''' <summary>
    ''' The zoom factor of the displayed image.
    ''' </summary>
    ''' <value></value>
    ''' <remarks>
    ''' </remarks>
    <DefaultValue(1)> _
    Public Property DisplayZoom() As Single
      Get
        Return _displayZoom
      End Get
      Set(ByVal Value As Single)
        Static previousDisplayImageBiggerThanControl As Boolean
        If Math.Abs(Value - 1.0) < 0.001 Then Value = 1.0

        If Not Me._bitmap Is Nothing Then

          'The new size of the displayed image.
          Dim newSize As New SizeF(Me.Bitmap.Width * Value, Me.Bitmap.Height * Value)

          If (Me._maximumDisplaySize.IsEmpty Or (newSize.Height <= Me._maximumDisplaySize.Height And newSize.Width <= Me._maximumDisplaySize.Width)) And _
            (newSize.Height >= Me.MinimumSize.Height And newSize.Width >= Me.MinimumSize.Height) Then
            Dim oldZoom As Single = Me._displayZoom
            Me._displayZoom = Value
            Debug.WriteLine("Set display zoom at " & Value, "AnnotatedImage.DisplayZoom")

            'We have to check if we have to reset or scale the display offset. 
            Dim currentDisplayImageBiggerThanControl As Boolean = DisplayImageBiggerThanControl()
            If currentDisplayImageBiggerThanControl = False Then
              _displayOffset.X = 0
              _displayOffset.Y = 0
            Else
              Dim zoomRatio As Double = oldZoom / Value

              _displayOffset.X = CInt(Math.Round(zoomRatio * Me._displayOffset.X))
              _displayOffset.Y = CInt(Math.Round(zoomRatio * Me._displayOffset.Y))

              ''We try to keep the center where it is!
              'Dim zoomRatio As Double = oldZoom / Value
              'Dim newControlSize As Size = ComputeControlSize()

              '_displayOffset.X = CInt(Math.Round(newControlSize.Width / 2 - zoomRatio * (Me.Width / 2 - Me._displayOffset.X)))
              '_displayOffset.Y = CInt(Math.Round(newControlSize.Height / 2 - zoomRatio * (Me.Height / 2 - Me._displayOffset.Y)))
              'SizeControl(newControlSize)
            End If
            SizeControl()
            RaiseEvent Changed(Me, New AnnotatedImageChangedEventArgs(AnnotatedImageChangedEventArgs.PropertyType.DisplayZoom))

            'Now do extra stuff like showing navigation and  magnifier
            previousDisplayImageBiggerThanControl = currentDisplayImageBiggerThanControl

          Else
            Debug.WriteLine("Display zoom not set due to minimum or maximum display size limitations", "AnnotatedImage.DisplayZoom")
          End If
        Else
          'No bitmap
          _displayZoom = Value
          SizeControl()
          RaiseEvent Changed(Me, New AnnotatedImageChangedEventArgs(AnnotatedImageChangedEventArgs.PropertyType.DisplayZoom))
        End If
      End Set
    End Property

    ''' <summary>
    ''' The physical zoom assumes a screenresolution of 96 dpi to compute a display zoom based on 
    ''' the physical size of the displayed image.
    ''' </summary>
    ''' <value></value>
    ''' <remarks>
    ''' </remarks>
    <Browsable(False), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)> _
    Public Property PhysicalZoom() As Single
      'This zoom is real, i.e. distance on screen versus real distance ....
      Get
        Return _displayZoom * _bitmap.HorizontalResolution / ScreenResolution
      End Get
      Set(ByVal Value As Single)
        If Not Me._bitmap Is Nothing Then
          Me.DisplayZoom = Value * _screenResolution / _bitmap.HorizontalResolution
          RaiseEvent Changed(Me, New AnnotatedImageChangedEventArgs(AnnotatedImageChangedEventArgs.PropertyType.PhysicalZoom))
        End If
      End Set
    End Property

    ''' <summary>
    ''' The size of the displayed image
    ''' </summary>
    ''' <value></value>
    ''' <remarks>
    ''' </remarks>
    <Browsable(False)> _
    Public Property DisplaySize() As Size
      Get
        If Not _bitmap Is Nothing Then
          Return New Size(CInt(Math.Round(_bitmap.Width * Me._displayZoom)), CInt(Math.Round(_bitmap.Height * Me._displayZoom)))
        Else
          Return New Size(0, 0)
        End If
      End Get
      Set(ByVal value As Size)
        If Not _bitmap Is Nothing Then
          Me.DisplayZoom = CSng(Math.Min(value.Width / _bitmap.Width, value.Height / _bitmap.Height))
        End If
      End Set
    End Property

    ''' <summary>
    ''' The size of the bitmap
    ''' </summary>
    ''' <value></value>
    ''' <remarks>
    ''' </remarks>
    <Browsable(False)> _
    Public ReadOnly Property BitmapSize() As Size
      Get
        If Me._bitmap Is Nothing Then
          Return New Size(0, 0)
        Else
          Return New Size(_bitmap.Width, _bitmap.Height)
        End If
      End Get
    End Property

    ''' <summary>
    ''' The resolution of the image in dpi
    ''' </summary>
    ''' <value></value>
    ''' <remarks>
    ''' </remarks>
    <Browsable(False)> _
    Public Property Resolution() As Single
      'This zoom is real, i.e. distance on screen versus real distance ....
      Get
        If Me._bitmap Is Nothing Then
          Return 0
        Else
          Return _bitmap.HorizontalResolution
        End If
      End Get
      Set(ByVal Value As Single)
        If Not Me._bitmap Is Nothing Then
          _bitmap.SetResolution(Value, Value)
          RaiseEvent Changed(Me, New AnnotatedImageChangedEventArgs(AnnotatedImageChangedEventArgs.PropertyType.Resolution))
        End If
      End Set
    End Property

    ''' <summary>
    ''' The number of bits per pixel.
    ''' </summary>
    ''' <value></value>
    ''' <remarks>
    ''' </remarks>
    <Browsable(False)> _
    Public ReadOnly Property ColorDepth() As Integer
      Get
        Return _bitmap.GetPixelFormatSize(_bitmap.PixelFormat)
      End Get
    End Property

    ''' <summary>
    ''' The size of the window used to average the pixel values under the mouse cursor.
    ''' </summary>
    ''' <value></value>
    ''' <remarks>
    ''' </remarks>
    <Browsable(False)> _
    Public Property PixelAveragingWindowSize() As Integer
      Get
        Return _averagingWindowSize
      End Get
      Set(ByVal Value As Integer)
        _averagingWindowSize = Value
        RaiseEvent Changed(Me, New AnnotatedImageChangedEventArgs(AnnotatedImageChangedEventArgs.PropertyType.PixelAveragingWindowSize))
      End Set
    End Property

    ''' <summary>
    ''' True during interactive creation of an annotation
    ''' </summary>
    ''' <value></value>
    ''' <remarks>
    ''' </remarks>
    Public ReadOnly Property CreatingAnnotation() As Boolean
      Get
        Return _creatingAnnotation
      End Get
    End Property

    ''' <summary>
    ''' Returns the annotation being created during interactive creation.
    ''' </summary>
    ''' <value></value>
    ''' <remarks>
    ''' </remarks>
    Public ReadOnly Property Annotation() As Annotation
      Get
        Return _annotation
      End Get
    End Property

    ''' <summary>
    ''' This indicates if XOR drawing has occured since last refresh, which is necessary for 
    ''' the routines drawing XOR (erasable) rectangles and lines...
    ''' </summary>
    ''' <value></value>
    ''' <remarks>
    ''' </remarks>
    Private Property Dirty() As Boolean
      Get
        Return _dirty
      End Get
      Set(ByVal Value As Boolean)
        _dirty = Value
      End Set
    End Property

#End Region

#Region "Control event handlers"
    Private Sub AnnotatedImage_ParentChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.ParentChanged
      If Not Me.Parent Is Nothing Then
        If TypeOf Me.TopLevelControl Is Form Then
          Me.MaximumSize = FormUtil.GetFormMaximumClientSize(CType(Me.TopLevelControl, Form))
        End If
      End If
    End Sub

    Private Sub AnnotatedImage_KeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles Me.KeyDown
      If _bitmap Is Nothing OrElse Me._busy Then Exit Sub
      Debug.WriteLine("Pressed key " & e.KeyCode, "AnnotatedImage.KeyDown")
      Select Case e.KeyCode
        Case Keys.Z
          If e.Control Then
            If _creatingAnnotation Then
              'Remove last added point in annotation creation  mode
              RemoveLastAnnotationCreationPoint()
            End If
          ElseIf e.Shift Then
            'Zoom in
            If Me._enableZoom Then Me.DisplayZoom = CSng(Me.DisplayZoom * 1.25)
          Else
            'Zoom out
            If Me._enableZoom Then Me.DisplayZoom = CSng(Me.DisplayZoom / 1.25)
          End If
        Case Keys.W
          If e.Shift Then
            'Decrease averaging window size
            If PixelAveragingWindowSize > 1 Then
              PixelAveragingWindowSize = PixelAveragingWindowSize - 2
            End If
          Else
            'Increase averaging window size
            PixelAveragingWindowSize = PixelAveragingWindowSize + 2
          End If
        Case Keys.F5
          'Cause refresh of client area
          Me.Draw()
        Case Keys.Delete
          'Delete selected annotation/annotation container. If a point is selected
          'inside the annotation, delete that
          If Not _creatingAnnotation And Not _locked Then
            If Not Me.SelectedItem Is Nothing Then
              If TypeOf Me.SelectedItem Is Annotation Then
                Dim ann As Annotation = CType(Me.SelectedItem, Annotation)

                If ann.SelectedPoint <> Annotation.NoPointSelected Then
                  Me.ConfirmDeleteAnnotationPoint(Nothing, ann, ann.SelectedPoint)
                Else
                  Me.ConfirmDeleteAnnotation(Nothing, ann)
                End If
              Else
                Me.ConfirmDeleteAnnotationContainer(Nothing, CType(Me.SelectedItem, AnnotationContainer))
              End If
            End If
          End If
        Case Keys.E
          'Expand current selected item
          If Not _creatingAnnotation And Not _locked Then
            If Not Me.SelectedItem Is Nothing Then
              If TypeOf Me.SelectedItem Is Annotation Then
                Dim distance As Integer = 1
                If e.Shift Then distance = 10
                Dim ann As Annotation = CType(Me.SelectedItem, Annotation)
                ann.Expand(distance)
              End If
            End If
          End If
        Case Keys.S
          'Expand current selected item
          If Not _creatingAnnotation And Not _locked Then
            If Not Me.SelectedItem Is Nothing Then
              If TypeOf Me.SelectedItem Is Annotation Then
                Dim distance As Integer = 1
                If e.Shift Then distance = 10
                Dim ann As Annotation = CType(Me.SelectedItem, Annotation)
                ann.Shrink(distance)
              End If
            End If
          End If
        Case Keys.Escape
          'Cancel interactive annotation creation
          If _creatingAnnotation Then
            CancelInteractiveAnnotationCreation()
          End If
        Case Keys.Enter
          'Finish interactive annotation creation
          If _creatingAnnotation Then
            'End annotation creation if permitted
            Dim redo As Boolean = False
            If e.Shift Then redo = True
            If CanOrMustEndInteractiveAnnotationCreation(True) Then EndInteractiveAnnotationCreation(redo)
          End If
        Case Keys.Back
          'Remove last point in interactive annotation creation
          If _creatingAnnotation Then
            RemoveLastAnnotationCreationPoint()
          End If
        Case Keys.ControlKey
          'Select annotation container cursor
          If Not _creatingAnnotation And Not _locked Then
            Dim oImgPt As PointF = ClientToImageCoordinates(Me.PointToClient(Me.MousePosition), _displayZoom, _displayOffset)
            Dim ann As Annotation = Me.Annotations.GetAnnotation(oImgPt)
            If Not ann Is Nothing Then Me.Cursor = Me.cursorSelectAnnotationContainer
          End If
        Case Keys.ShiftKey
          'Select annotation point cursor
          If Not _creatingAnnotation And Not _locked Then
            Dim oImgPt As PointF = ClientToImageCoordinates(Me.PointToClient(Me.MousePosition), _displayZoom, _displayOffset)
            Dim ann As Annotation = Me.Annotations.GetAnnotation(oImgPt)
            If Not ann Is Nothing Then
              If Me.Annotations.IsPointVisible(ann, oImgPt) > -1 Then
                Me.Cursor = Me.cursorSelectPoint
              Else
                Me.Cursor = Me.cursorCross
              End If
            Else
              Me.Cursor = Me.cursorCross
            End If
          End If
      End Select
    End Sub

    Private Sub AnnotatedImage_KeyUp(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles Me.KeyUp
      If _bitmap Is Nothing OrElse Me._busy Then Exit Sub
      Debug.WriteLine("Released key " & e.KeyCode, "AnnotatedImage.KeyUp")

      Select Case e.KeyCode
        Case Keys.ControlKey, Keys.ShiftKey
          'Back to normal cursor
          If Not _creatingAnnotation Then
            Dim oImgPt As PointF = ClientToImageCoordinates(Me.PointToClient(Me.MousePosition), _displayZoom, _displayOffset)
            Dim ann As Annotation = Me.Annotations.GetAnnotation(oImgPt)
            If Not ann Is Nothing Then
              Me.Cursor = Me.cursorSelectAnnotation
            Else
              Me.Cursor = Me.cursorCross
            End If
          End If
        Case Keys.Up
          'Move selected item
          If Not _creatingAnnotation And Not _locked Then
            If e.Shift Then
              MoveSelectedItem(0, -5)
            ElseIf e.Control Then
              MoveSelectedItem(0, -10)
            Else
              MoveSelectedItem(0, -1)
            End If
          End If
        Case Keys.Down
          If Not _creatingAnnotation And Not _locked Then
            If e.Shift Then
              MoveSelectedItem(0, 5)
            ElseIf e.Control Then
              MoveSelectedItem(0, 10)
            Else
              MoveSelectedItem(0, 1)
            End If
          End If
        Case Keys.Left
          If Not _creatingAnnotation And Not _locked Then
            If e.Shift Then
              MoveSelectedItem(-5, 0)
            ElseIf e.Control Then
              MoveSelectedItem(-10, 0)
            Else
              MoveSelectedItem(-1, 0)
            End If
          End If
        Case Keys.Right
          If Not _creatingAnnotation And Not _locked Then
            If e.Shift Then
              MoveSelectedItem(5, 0)
            ElseIf e.Control Then
              MoveSelectedItem(10, 0)
            Else
              MoveSelectedItem(1, 0)
            End If
          End If
      End Select
    End Sub

    Private Sub AnnotatedImage_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseDown
      If _bitmap Is Nothing OrElse Me._busy Then Exit Sub

      Debug.WriteLine("Mouse button " & e.Button & " at " & e.X & "," & e.Y, "AnnotatedImage.MouseDown")
      Dim objClientPt As New Point(e.X, e.Y)
      Me.moving = False
      Select Case e.Button
        Case MouseButtons.Middle
          'Move image if appropriate
          'On some computers the middle mouse button does not work well, so we bind the same 
          'behavior under the Ctrl-left mouse button. NOT ANYMORE ...
          If DisplayImageBiggerThanControl() Then
            Debug.WriteLine("Initiating image drag", "AnnotatedImage.MouseDown")
            Me.Cursor = Me.cursorMove
            lastMousePos.X = e.X
            lastMousePos.Y = e.Y

            'Disable drawing of the annotations for smooth moving of the image
            'Me.currentAnnotationsVisibility = Me.Annotations.Visible
            'Me.Annotations.Visible = False
          Else
            'Activate the magnifier
            'Me.MagnifierVisible = True
          End If
        Case MouseButtons.Left
          If _creatingAnnotation Then
            'Insert point in annotation and redraw
            Debug.WriteLine("Adding point", "AnnotatedImage.MouseDown")
            AddAnnotationCreationPoint(objClientPt, False)
          Else
            'Check for selection
            If Not _locked Then
              Dim oImgPt As PointF = ClientToImageCoordinates(objClientPt, _displayZoom, _displayOffset)
              Dim ann As Annotation = Me.Annotations.GetAnnotation(oImgPt)
              If Not ann Is Nothing Then
                If Me.ModifierKeys = Keys.None Then
                  If ann.Equals(Me.SelectedItem) Then
                    'If we are over a selected item, we may initiate a move, show move cursor.

                    If ann.SelectedPoint <> Annotation.NoPointSelected Then
                      'Annotation and point are selected, allow move if cursor on point only
                      If Me._annotations.IsPointVisible(ann, oImgPt) = ann.SelectedPoint Then
                        Debug.WriteLine("Initiating annotation point move (selected)", "AnnotatedImage.MouseDown")
                        Me.moving = True
                        lastMousePos.X = e.X
                        lastMousePos.Y = e.Y
                        Me.Cursor = Me.cursorMove
                      End If
                    Else
                      'Annotation is selected, allow move
                      Debug.WriteLine("Initiating annotation move (selected)", "AnnotatedImage.MouseDown")
                      Me.moving = True
                      lastMousePos.X = e.X
                      lastMousePos.Y = e.Y
                      Me.Cursor = Me.cursorMove
                    End If
                  ElseIf ann.Parent.Equals(Me.SelectedItem) Then
                    'Container is selected, allow move
                    Debug.WriteLine("Initiating annotation container move (selected)", "AnnotatedImage.MouseDown")
                    Me.moving = True
                    lastMousePos.X = e.X
                    lastMousePos.Y = e.Y
                    Me.Cursor = Me.cursorMove
                  End If
                End If

                'We are not over a selected item, select current object under mouse and initiate move
                If Not Me.moving Then
                  'We can select either a point (SHIFT), the annotation or the container (CTRL)
                  'Cursor directly becomes a move!
                  Select Case Me.ModifierKeys
                    Case Keys.Control
                      ann.Parent.Selected = True
                      Me.Cursor = Me.cursorMove
                      lastMousePos.X = e.X
                      lastMousePos.Y = e.Y
                      Me.moving = True
                      Debug.WriteLine("Selecting annotation container and initiating move", "AnnotatedImage.MouseDown")
                    Case Keys.Shift
                      'Check if we actually hit the point
                      Dim index As Integer = Me._annotations.IsPointVisible(ann, oImgPt)
                      If index > -1 Then
                        ann.Selected = True
                        ann.SelectedPoint = index
                        lastMousePos.X = e.X
                        lastMousePos.Y = e.Y
                        Me.Cursor = Me.cursorMove
                        Me.moving = True
                        Debug.WriteLine("Selecting annotation point and intiating move", "AnnotatedImage.MouseDown")
                      Else
                        If Not Me.SelectedItem Is Nothing Then Me.SelectedItem.Selected = False
                      End If
                    Case Else
                      ann.Selected = True
                      lastMousePos.X = e.X
                      lastMousePos.Y = e.Y
                      Me.Cursor = Me.cursorMove
                      Me.moving = True
                      Debug.WriteLine("Selecting annotation and intiating move", "AnnotatedImage.MouseDown")
                  End Select
                End If
              Else
                'Deselect any if any selected 
                If Not Me.SelectedItem Is Nothing Then Me.SelectedItem.Selected = False
              End If
            End If
          End If
        Case MouseButtons.Right
          'Show context sensitive menus, if enabled
          If _enableContextMenus = True Then
            If _creatingAnnotation Then
              'Show popup menu with options available while creating an annotation: Cancel, Done, Undo.
              Me.creatingAnnotationMenu.Show(Me, objClientPt)
            Else
              'Show popup menu with properties  if enabled
              Dim annItem As Annotation = Me.Annotations.GetAnnotation(ClientToImageCoordinates(objClientPt, _displayZoom, _displayOffset))
              If Not annItem Is Nothing Then

                'If the object we are over is the same as the selected one, then we use that
                If annItem.Equals(Me.SelectedItem) Then
                  If Me.ModifierKeys = Keys.Control Then
                    annItem.Parent.Selected = True
                    Me.annotationContainerMenu.Show(Me, objClientPt)
                  Else
                    Me.annotationMenu.Show(Me, objClientPt)
                  End If
                ElseIf annItem.Parent.Equals(Me.SelectedItem) Then
                  Me.annotationContainerMenu.Show(Me, objClientPt)
                Else
                  'We are not over any currently selected item, select current item and show menu
                  If Me.ModifierKeys = Keys.Control Then
                    annItem.Parent.Selected = True
                    Me.annotationContainerMenu.Show(Me, objClientPt)
                  Else
                    annItem.Selected = True
                    Me.annotationMenu.Show(Me, objClientPt)
                  End If
                End If
              Else
                'When over the image we merge the masks and annotations menu!
                Dim imageMenu As New ContextMenu
                imageMenu.MergeMenu(Me.annotationsMenu)
                imageMenu.Show(Me, objClientPt)
              End If
            End If
          End If
      End Select
    End Sub

    Private Sub AnnotatedImage_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseMove
      If _bitmap Is Nothing OrElse Me._busy Then Exit Sub

      'Key events do not get processed when the control does not have focus, so we set focus when the mouse
      'moves over the control
      If Me.Focused = False And Me._enableAutoFocus Then Me.Focus()

      Dim oClientPt As New Point(e.X, e.Y)
      Select Case e.Button
        Case MouseButtons.Middle
          'This mousebutton is erratic on XP, so we bind the same behavior under Shift. NOT ANYMORE
          If DisplayImageBiggerThanControl() Then
            'Adjust DisplayOffset
            Dim objCurrentOffset As Point = Me.DisplayOffset
            Me.DisplayOffset = New Point(CInt(Math.Round(objCurrentOffset.X + (lastMousePos.X - e.X) / _displayZoom)), _
                                        CInt(Math.Round(objCurrentOffset.Y + (lastMousePos.Y - e.Y) / _displayZoom)))
            lastMousePos.X = e.X
            lastMousePos.Y = e.Y
          Else
            'Me.MagnifierVisible = True
          End If
        Case MouseButtons.Left
          If _creatingAnnotation Then
            'Insert point in annotation and redraw. This allows continuous drawing when holding the left button down.
            DrawInteractiveCreationErasableLines(oClientPt)
            AddAnnotationCreationPoint(oClientPt, False)
          ElseIf Not Me._locked Then
            'If we want to move an annotation or annotation point we can do this here
            'In order to avoid spurious moves when selecting annotations after interactive creation, we make sure 
            'the moving variable is true
            If Me.moving = True And Me.ModifierKeys = Keys.None Then
              Dim oMove As New PointF((e.X - lastMousePos.X) / _displayZoom, (e.Y - lastMousePos.Y) / _displayZoom)
              If TypeOf Me.SelectedItem Is Annotation Then
                'Move the annotation, or a point from the annotation
                If Math.Abs(oMove.X) > 0 And Math.Abs(oMove.Y) > 0 Then
                  Dim poly As Annotation = CType(Me.SelectedItem, Annotation)
                  If poly.SelectedPoint <> Annotation.NoPointSelected Then
                    Debug.WriteLine("Moving annotation point " & poly.SelectedPoint & " by " & oMove.ToString)
                    poly.Move(poly.SelectedPoint, oMove.X, oMove.Y)
                  Else
                    Debug.WriteLine("Moving annotation by " & oMove.ToString)
                    poly.Move(oMove.X, oMove.Y)
                  End If
                  lastMousePos.X = e.X
                  lastMousePos.Y = e.Y
                End If
              ElseIf TypeOf Me.SelectedItem Is AnnotationContainer Then
                'Move all the annotations in the container
                If Math.Abs(oMove.X) > 0 And Math.Abs(oMove.Y) > 0 Then
                  Dim polySet As AnnotationContainer = CType(Me.SelectedItem, AnnotationContainer)
                  polySet.Move(oMove.X, oMove.Y)
                  lastMousePos.X = e.X
                  lastMousePos.Y = e.Y
                End If
              End If
            End If
          End If
        Case MouseButtons.None
          If _creatingAnnotation Then
            DrawInteractiveCreationErasableLines(oClientPt)
          ElseIf Not Me._locked Then
            'Check if we hit an annotation and show possible selections. 
            Dim oImgPt As PointF = ClientToImageCoordinates(oClientPt, _displayZoom, _displayOffset)
            Dim ann As Annotation = Me.Annotations.GetAnnotation(oImgPt)
            If Not ann Is Nothing Then
              'If we are over a selected item, we may initiate a move, show move cursor.
              Dim doMove As Boolean = False

              If ann.Equals(Me.SelectedItem) And Me.ModifierKeys = Keys.None Then
                If ann.SelectedPoint <> Annotation.NoPointSelected Then
                  'Annotation and point are selected, allow move if cursor on selected point only
                  If Me._annotations.IsPointVisible(ann, oImgPt) = ann.SelectedPoint Then
                    doMove = True
                    lastMousePos.X = e.X
                    lastMousePos.Y = e.Y
                    Me.Cursor = Me.cursorMove
                  End If
                Else
                  'Annotation is selected, allow move
                  doMove = True
                  lastMousePos.X = e.X
                  lastMousePos.Y = e.Y
                  Me.Cursor = Me.cursorMove
                End If
              ElseIf ann.Parent.Equals(Me.SelectedItem) Then
                'Container is selected, allow move
                doMove = True
                lastMousePos.X = e.X
                lastMousePos.Y = e.Y
                Me.Cursor = Me.cursorMove
              End If

              If Not doMove Then
                'No move has been initiated, a selection is possible

                'Change to parent container if CTRL modifier is on
                'Change to point if SHIFT modifier is on
                Select Case Me.ModifierKeys
                  Case Keys.Control
                    Me.Cursor = Me.cursorSelectAnnotationContainer
                  Case Keys.Shift
                    If Me.Annotations.IsPointVisible(ann, oImgPt) > -1 Then
                      Me.Cursor = Me.cursorSelectPoint
                    End If
                  Case Else
                    Me.Cursor = Me.cursorSelectAnnotation
                End Select
              End If
            Else
              Me.Cursor = Me.cursorCross
            End If
          End If
      End Select
    End Sub

    Private Sub AnnotatedImage_DoubleClick(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.DoubleClick
      If _bitmap Is Nothing OrElse Me._busy Then Exit Sub

      'Show annotation manager if not on annotation, else show property dialog.
      'Do not show when creating an annotation
      If Not _creatingAnnotation Then
        If _enablePropertyDialogs Then
          Dim objClientPt As Point = Me.PointToClient(Control.MousePosition)
          Dim objItem As Annotation = Me.Annotations.GetAnnotation(ClientToImageCoordinates(objClientPt, _displayZoom, _displayOffset))
          If Not objItem Is Nothing Then
            If Control.ModifierKeys = Keys.Control Then
              Me.SetAnnotationContainerProperties(objItem.Parent)
            Else
              Me.SetAnnotationProperties(objItem)
            End If
          End If
        End If
      Else
        'End annotation creation if permitted
        Dim redo As Boolean = False
        If Control.ModifierKeys = Keys.Shift Then redo = True
        If CanOrMustEndInteractiveAnnotationCreation(True) Then EndInteractiveAnnotationCreation(redo)
      End If
    End Sub

    Private Sub AnnotatedImage_MouseUp(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseUp
      If _bitmap Is Nothing OrElse Me._busy Then Exit Sub
      Select Case e.Button
        Case MouseButtons.Left
          'This sorts out any misalignment after dragging of annotations or points
          If Not Me.CreatingAnnotation Then Me.Draw()
        Case MouseButtons.Right
          Me.Cursor = System.Windows.Forms.Cursors.Default
        Case MouseButtons.Middle
      End Select
    End Sub

    Private Sub AnnotatedImage_MouseWheel(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseWheel
      If _bitmap Is Nothing OrElse Me._busy Then Exit Sub

      Debug.WriteLine("Mouse wheel Delta " & e.Delta, "AnnotatedImage.MouseWheel")
      If Me._enableZoom Then
        If e.Delta > 0 Then
          Me.DisplayZoom = CSng(Me.DisplayZoom / 1.25)
        Else
          Me.DisplayZoom = CSng(Me.DisplayZoom * 1.25)
        End If
      End If
    End Sub

    Private Sub MoveSelectedItem(ByVal dx As Single, ByVal dy As Single)
      If Not Me.SelectedItem Is Nothing Then
        If TypeOf Me.SelectedItem Is Annotation Then
          Dim ann As Annotation = CType(Me.SelectedItem, Annotation)
          If ann.SelectedPoint = Annotation.NoPointSelected Then
            ann.Move(dx, dy)
          Else
            ann.Move(ann.SelectedPoint, dx, dy)
          End If
        ElseIf TypeOf Me.SelectedItem Is AnnotationContainer Then
          Dim container As AnnotationContainer = CType(Me.SelectedItem, AnnotationContainer)
          container.Move(dx, dy)
        End If
      End If
    End Sub
#End Region

#Region "Subclasses and collection event handlers"
    Private Sub Masks_Changed(ByVal sender As Object, ByVal e As CollectionChangedEventArgs) Handles _masks.Changed
      'No need to block this event when updating, it will not happen in rapid sucession!
      'Draw WILL be blocked during updating though ....
      If Me.Updating Then Exit Sub
      If e.Type = CollectionChangedEventArgs.EventType.ItemsDeleted Or e.Type = CollectionChangedEventArgs.EventType.ItemDeleted Then CheckSelectedItem()
      RaiseEvent MasksChanged(sender, e)
      Me.Draw()
    End Sub

    Private Sub Annotations_ChangedEvent(ByVal sender As Object, ByVal e As CollectionChangedEventArgs) Handles _annotations.Changed
      _annotationsSaved = False
      If Me.Updating Then Exit Sub
      RaiseEvent AnnotationsChanged(sender, e)
      Me.Draw()
    End Sub

    Private Sub AnnotationContainer_ChangedEvent(ByVal sender As Object, ByVal e As CollectionChangedEventArgs) Handles _annotations.AnnotationContainerChanged
      _annotationsSaved = False
      If Me.Updating Then Exit Sub
      RaiseEvent AnnotationContainerChanged(sender, e)
      Me.Draw()
    End Sub

    Private Sub Annotation_ChangedEvent(ByVal sender As Object, ByVal e As CollectionChangedEventArgs) Handles _annotations.AnnotationChanged
      _annotationsSaved = False
      If Me.Updating Then Exit Sub
      RaiseEvent AnnotationChanged(sender, e)
      Me.Draw()
    End Sub

    Private Sub ItemSelectedChanged(ByVal sender As ISelectable) Handles _annotations.SelectedChanged, _masks.SelectedChanged
      If sender.Selected = True Then
        'Deselect any existing selection if present
        If Not Me._selectedItem Is Nothing Then Me._selectedItem.Selected = False

        'Set selected Item
        Me._selectedItem = sender
      Else
        'Set selected Item. We send nothing on deselect
        Me._selectedItem = Nothing
      End If
      'Raise event if not updating
      If Not Me.Updating Then
        RaiseEvent ItemSelected(Me._selectedItem)
        Me.Draw()
      End If
    End Sub
#End Region

#Region "Context menu creation, extension and event handling"
    ''' <summary>
    ''' Add a menu item to the context sensitive menus of the annotations, annotation containers and masks
    ''' </summary>
    ''' <param name="strCaption"></param>
    ''' <param name="iMenu"></param>
    ''' <param name="f"></param>
    ''' <remarks>This is fine for very simple actions that consist of just 1 step.
    ''' </remarks>
    Public Sub AddContextMenuItem(ByVal strCaption As String, _
                                  ByVal iMenu As ContextMenus, _
                                  ByVal f As System.EventHandler)
      Select Case iMenu
        Case ContextMenus.Annotation
          Me.annotationMenu.MenuItems.Add(strCaption, f)
        Case ContextMenus.AnnotationContainer
          Me.annotationContainerMenu.MenuItems.Add(strCaption, f)
        Case ContextMenus.AnnotationNew
          Me.creatingAnnotationMenu.MenuItems.Add(strCaption, f)
        Case ContextMenus.Mask
          Me.MaskMenu.MenuItems.Add(strCaption, f)
        Case ContextMenus.Masks
          Me.MasksMenu.MenuItems.Add(strCaption, f)
      End Select
    End Sub

    ''' <summary>
    ''' Reset all the context menus to their default items
    ''' </summary>
    ''' <remarks>
    ''' </remarks>
    Public Sub ResetContextMenuItems()
      Me.annotationsMenu.MenuItems.Clear()
      Me.creatingAnnotationMenu.MenuItems.Clear()
      Me.annotationsMenu.MenuItems.Clear()
      Me.annotationContainerMenu.MenuItems.Clear()
      CreateContextMenus()
    End Sub

    Private Sub CreateContextMenus()
      If Me.creatingAnnotationMenu Is Nothing Then Me.creatingAnnotationMenu = New ContextMenu
      Me.creatingAnnotationMenu.MenuItems.Add("Done (Enter)", AddressOf AnnotationCreationMenu_Click)
      Me.creatingAnnotationMenu.MenuItems.Add("Done and redo (Shift-Enter)", AddressOf AnnotationCreationMenu_Click)
      Me.creatingAnnotationMenu.MenuItems.Add("Cancel (Esc)", AddressOf AnnotationCreationMenu_Click)
      Me.creatingAnnotationMenu.MenuItems.Add("Undo (Ctrl-z)", AddressOf AnnotationCreationMenu_Click)
      'Me.creatingAnnotationMenu.MenuItems.Add("-")

      If Me.annotationMenu Is Nothing Then Me.annotationMenu = New ContextMenu
      Me.annotationMenu.MenuItems.Add("Expand/Shrink", AddressOf AnnotationMenu_Click)
      Me.annotationMenu.MenuItems.Add("Quantize", AddressOf AnnotationMenu_Click)
      Me.annotationMenu.MenuItems.Add("Delete", AddressOf AnnotationMenu_Click)
      Me.annotationMenu.MenuItems.Add("Delete set", AddressOf AnnotationMenu_Click)
      Me.annotationMenu.MenuItems.Add("-")
      Me.annotationMenu.MenuItems.Add("Copy to clipboard", AddressOf AnnotationMenu_Click)
      Me.annotationMenu.MenuItems.Add("Hide/Show", AddressOf AnnotationMenu_Click)
      Me.annotationMenu.MenuItems.Add("-")
      Me.annotationMenu.MenuItems.Add("Properties", AddressOf AnnotationMenu_Click)

      If Me.annotationContainerMenu Is Nothing Then Me.annotationContainerMenu = New ContextMenu
      Me.annotationContainerMenu.MenuItems.Add("New annotation", AddressOf AnnotationContainerMenu_Click)
      Me.annotationContainerMenu.MenuItems.Add("Delete", AddressOf AnnotationContainerMenu_Click)
      Me.annotationContainerMenu.MenuItems.Add("-")
      Me.annotationContainerMenu.MenuItems.Add("Hide/Show", AddressOf AnnotationContainerMenu_Click)
      Me.annotationContainerMenu.MenuItems.Add("-")
      Me.annotationContainerMenu.MenuItems.Add("Properties", AddressOf AnnotationContainerMenu_Click)

      If Me.annotationsMenu Is Nothing Then Me.annotationsMenu = New ContextMenu
      Me.annotationsMenu.MenuItems.Add("New container", AddressOf annotationsMenu_Click)
      Me.annotationsMenu.MenuItems.Add("New annotation in default container", AddressOf annotationsMenu_Click)
      Me.annotationsMenu.MenuItems.Add("Delete all annotations", AddressOf annotationsMenu_Click)
      Me.annotationsMenu.MenuItems.Add("-")
      Me.annotationsMenu.MenuItems.Add("Hide/Show all annotations", AddressOf annotationsMenu_Click)

    End Sub

    Private Sub AnnotationMenu_Click(ByVal sender As Object, ByVal e As System.EventArgs)
      Dim oMenuItem As MenuItem = CType(sender, MenuItem)
      Dim ann As Annotation = CType(_selectedItem, Annotation)

      Select Case oMenuItem.Text
        Case "Properties"
          Me.SetAnnotationProperties(ann)
        Case "Delete"
          Me.ConfirmDeleteAnnotation(Nothing, ann)
        Case "Delete container"
          Me.ConfirmDeleteAnnotationContainer(Nothing, ann.Parent)
        Case "Copy to clipboard"
          Dim strText As String = ann.ToString(",", ",")
          Clipboard.SetDataObject(strText)
          MessageBox.Show("The list of coordinates has been placed on the clipboard")
        Case "Hide/Show"
          ann.Visible = Not ann.Visible
        Case "Quantize"
          Dim strLength As String = InputBox("Enter the minimum distance between annotation points in pixel units", "Quantize", "4")
          Dim objCulture As New System.Globalization.CultureInfo("")
          Dim length As Single = Single.Parse(strLength, objCulture)
          ann.Quantize(length, 2 * length)
        Case "Expand/Shrink"
          Dim strLength As String = InputBox("Enter the distance to expand or shrink (negative value) in pixel units", "Expand/Shrink", "1")
          Dim objCulture As New System.Globalization.CultureInfo("")
          Dim length As Integer = Integer.Parse(strLength, objCulture)
          ann.Expand(length)
      End Select
    End Sub

    Private Sub AnnotationContainerMenu_Click(ByVal sender As Object, ByVal e As System.EventArgs)
      Dim oMenuItem As MenuItem = CType(sender, MenuItem)
      Dim oAnnCon As AnnotationContainer = CType(_selectedItem, AnnotationContainer)
      Select Case oMenuItem.Text
        Case "Properties"
          Me.SetAnnotationContainerProperties(oAnnCon)
        Case "New annotation"
          Me.CreateNewAnnotation(oAnnCon)
        Case "Delete"
          Me.ConfirmDeleteAnnotationContainer(Nothing, oAnnCon)
        Case "Hide/Show"
          oAnnCon.Visible = Not oAnnCon.Visible
      End Select
    End Sub

    Private Sub annotationsMenu_Click(ByVal sender As Object, ByVal e As System.EventArgs)
      Dim oMenuItem As MenuItem = CType(sender, MenuItem)
      Select Case oMenuItem.Text
        Case "New annotation container"
          Me.CreateNewAnnotationContainer()
        Case "New annotation in default container"
          Me.CreateNewAnnotationInDefaultContainer()
        Case "Delete all annotations"
          Me.ConfirmDeleteAllAnnotationContainers(Nothing)
        Case "Hide/Show all annotation containers"
          Me.Annotations.Visible = Not Me.Annotations.Visible
      End Select
    End Sub

    Private Sub AnnotationCreationMenu_Click(ByVal sender As Object, ByVal e As System.EventArgs)
      Dim oMenuItem As MenuItem = CType(sender, MenuItem)
      Select Case oMenuItem.Index
        Case 0
          If CanOrMustEndInteractiveAnnotationCreation(True) Then EndInteractiveAnnotationCreation()
        Case 1
          If CanOrMustEndInteractiveAnnotationCreation(True) Then EndInteractiveAnnotationCreation(True)
        Case 2
          CancelInteractiveAnnotationCreation()
        Case 3
          RemoveLastAnnotationCreationPoint()
      End Select
    End Sub

#End Region

#Region "Draw"
    Protected Overrides Sub OnPaint(ByVal pe As System.Windows.Forms.PaintEventArgs)
      'Add your custom paint code here.
      'Note that we also check the Updating event here, as the OnPaint method can also be
      'called from CLR code
      MyBase.OnPaint(pe)
      If Not Me._updating Then
        Debug.WriteLine("OnPaint event for clip rectangle " & pe.ClipRectangle.ToString)
        Draw(pe.Graphics, pe.ClipRectangle, _displayZoom, _displayOffset)
        Me.Dirty = False
      Else
        Debug.WriteLine("OnPaint event canceled")
      End If
    End Sub

    ''' <summary>
    ''' Return a 24-bit bitmap with all the annotations rendered into it.
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks>The annotations must be visible!
    ''' </remarks>
    Public Function GetRenderedBitmap() As Bitmap
      Dim image As New Bitmap(Me.Bitmap.Width, Me.Bitmap.Height, Imaging.PixelFormat.Format24bppRgb)
      Me._annotations.RenderIntoBitmap(image)

      'Dim g As Graphics = Graphics.FromImage(_bitmap)
      'Draw(g, New Rectangle(0, 0, Me.Bitmap.Width, Me.Bitmap.Height), 1, New Point(0, 0))
      'g.Dispose()
      Return image
    End Function

    ''' <summary>
    ''' Invalidate only a part of the client region, useful when very fast limited updates are necessary
    ''' </summary>
    ''' <param name="clipRect"></param>
    ''' <remarks>
    ''' </remarks>
    Private Overloads Sub Draw(ByVal clipRect As Rectangle)
      Draw(False, clipRect)
    End Sub

    ''' <summary>
    ''' Invalidate only a part of the client region, useful when very fast limited updates are necessary
    ''' </summary>
    ''' <param name="bForce"></param>
    ''' <param name="clipRect"></param>
    ''' <remarks>
    ''' </remarks>
    Private Overloads Sub Draw(ByVal bForce As Boolean, ByVal clipRect As Rectangle)
      If Not Me._updating Or bForce = True Then
        Me.Dirty = True
        Me.Invalidate(clipRect)
        Debug.WriteLine("Requesting rectangle draw", "AnnotatedImage.Draw")
      Else
        Debug.WriteLine("Rectangle Draw request canceled during update", "AnnotatedImage.Draw")
      End If
    End Sub

    Delegate Sub DrawDelegate(ByVal bForce As Boolean)

    ''' <summary>
    ''' Draw the annotations. In order too avoid multiple unnecessary redraws this will not occur 
    ''' if the updating property is set true, except if bForce is true
    ''' </summary>
    ''' <param name="bForce">Force redraw regardless of the updating parameter. Use this carefully for OS commanded redraw.</param>
    ''' <remarks>This function will trigger the overridden OnPaint method. Note that we do not use the Update method to force synchronous paint!
    ''' Sometimes this function is called from another thread so we check for this and use invoke if required.
    ''' </remarks>
    Public Overloads Sub Draw(ByVal bForce As Boolean)
      If Me.InvokeRequired Then
        Dim d As New DrawDelegate(AddressOf Draw)
        Me.Invoke(d, New Object() {bForce})
      Else
        If Not Me._updating Or bForce = True Then
          Dim crs As Cursor = Me.Cursor
          Me.Cursor = Cursors.WaitCursor

          Me.Dirty = True
          Me.Invalidate()
          Debug.WriteLine("Requesting draw", "AnnotatedImage.Draw")
          Me.Cursor = crs
        Else
          Debug.WriteLine("Draw request canceled", "AnnotatedImage.Draw")
        End If
      End If
    End Sub

    ''' <summary>
    ''' Draw the annotations. In order too avoid multiple unnecessary redraws this will not occur 
    ''' if the updating property is set true.
    ''' </summary>
    ''' <remarks>This function will trigger the overridden OnPaint method
    ''' </remarks>
    Public Overloads Sub Draw()
      Me.Draw(False)
    End Sub

    ''' <summary>
    ''' Draw the image, masks and annotations on the given graphics context, using the given clip rectangle.
    ''' </summary>
    ''' <param name="g"></param>
    ''' <param name="clipRect"></param>
    ''' <remarks>This routine can also be used to render the annotations in an image...
    ''' </remarks>
    Private Overloads Sub Draw(ByVal g As Graphics, ByVal clipRect As Rectangle, ByVal zoom As Single, ByVal offset As Point)
      'Draw the bitmap ...
      If Not _bitmap Is Nothing Then
        'Paint the bitmap onto the control, taking the cliprectangle into account

        'Quality for scaling of images
        If Me._highQualityZoom Then
          g.InterpolationMode = InterpolationMode.HighQualityBilinear
        Else
          g.InterpolationMode = InterpolationMode.NearestNeighbor
        End If

        'Dim dstRect As New RectangleF(clipRect.X, clipRect.Y, clipRect.Width, clipRect.Height)
        Dim srcRect As Rectangle = Rectangle.Round(New RectangleF(offset.X + clipRect.X / zoom, offset.Y + clipRect.Y / zoom, _
                                      clipRect.Width / zoom, clipRect.Height / zoom))
        Debug.WriteLine("Redrawing bitmap from rectangle " & srcRect.ToString & " onto " & clipRect.ToString & _
          " (Zoom " & zoom & ", offset " & offset.ToString & ")", "AnnotatedImage.Draw")

        If _optimizeBitmapDisplay Then
          g.DrawImage(_optimizedBitmap, clipRect, srcRect, GraphicsUnit.Pixel)
        Else
          g.DrawImage(_bitmap, clipRect, srcRect, GraphicsUnit.Pixel)
        End If

        'Draw masks in overlay if present. Note that these masks can be undersampled, and are best
        'scaled without interpolation
        If _drawMasks Then
          Me._masks.Draw(g, Me._bitmap, srcRect, clipRect)
        End If
      Else
        Debug.WriteLine("No bitmap to redraw!", "AnnotatedImage.Draw")
      End If

      'Draw annotatations
      If _drawAnnotations Then
        Me._annotations.Draw(g, zoom, offset)
      Else
        Debug.WriteLine("Annotations are not visible!", "AnnotatedImage.Draw")
      End If

      'Draw erasable lines if required, as these get erased by a redraw
      If _creatingAnnotation Then
        Dim pt As Point = Me.PointToClient(Me.MousePosition)
        'Only proceed if mouse is in window (client area)
        If Me.ClientRectangle.Contains(pt) Then
          DrawInteractiveCreationErasableLines(pt)
        End If
      End If
    End Sub

    ''' <summary>
    ''' Draw an erasable rectangle (XOR-drawing) on the bitmap
    ''' </summary>
    ''' <param name="objRect"></param>
    ''' <remarks>Rectange is in client coordinates
    ''' </remarks>
    Public Sub DrawErasableRectangle(ByVal objRect As Rectangle)
      'Draw an eraseble rectangle.
      'Input is in client coordinates, static old rectangle is in screen coordinates!
      'Checks that we do not draw outside client area
      Static objOldRect As Rectangle

      'Take a middle of the road Color
      Dim objColor As New Color
      objColor = Color.Gray

      'Only erase if required, e.g. not when control has been refreshed ...
      If Me.Dirty = True And objOldRect.Width > 0 Then
        ControlPaint.DrawReversibleFrame(objOldRect, objColor, FrameStyle.Dashed)
      End If

      'Make sure we are not outside the client area
      objRect.Intersect(New Rectangle(0, 0, Me.Width, Me.Height))

      objOldRect = Me.RectangleToScreen(objRect)
      ControlPaint.DrawReversibleFrame(objOldRect, objColor, FrameStyle.Dashed)
      Me.Dirty = True
    End Sub

    ''' <summary>
    ''' Draw an erasable line.
    ''' </summary>
    ''' <param name="objPt1"></param>
    ''' <param name="objPt2"></param>
    ''' <remarks>Points are in client coordinates
    ''' </remarks>
    Public Sub DrawErasableLine(ByVal objPt1 As Point, ByVal objPt2 As Point)
      'Draw an eraseble line.
      'Pts are in client coordinates
      'static old Pts are in screen coordinates!
      Static objOldPt1 As Point
      Static objOldPt2 As Point

      'Take a middle of the road ColorUtil
      Dim objColor As New Color
      objColor = Color.Gray

      'Avoid trying to erase a rectangle if canvas has just been redrawn!
      If Me.Dirty Then
        ControlPaint.DrawReversibleLine(objOldPt1, objOldPt2, objColor)
      End If

      'Convert to screen coordinates
      objOldPt1 = Me.PointToScreen(objPt1)
      objOldPt2 = Me.PointToScreen(objPt2)
      ControlPaint.DrawReversibleLine(objOldPt1, objOldPt2, objColor)

      Me.Dirty = True
    End Sub

    ''' <summary>
    ''' Draw a series of erasable lines
    ''' </summary>
    ''' <param name="oPts"></param>
    ''' <remarks>Points are in client coordinates
    ''' </remarks>
    Public Sub DrawErasableLines(ByVal oPts() As Point)
      'Draw eraseble lines.
      'Pts are in client coordinates
      'static old Pts are in screen coordinates!
      Static oOldPts() As Point

      'Take a middle of the road ColorUtil
      Dim objColor As New Color, i As Integer
      objColor = Color.Gray

      'Avoid trying to erase a rectangle if canvas has just been redrawn!
      If Me.Dirty And Not oOldPts Is Nothing Then
        For i = 0 To oOldPts.Length - 2
          ControlPaint.DrawReversibleLine(oOldPts(i), oOldPts(i + 1), objColor)
        Next
      End If

      'Convert to screen coordinates
      ReDim oOldPts(oPts.Length - 1)
      For i = 0 To oPts.Length - 2
        oOldPts(i) = Me.PointToScreen(oPts(i))
        oOldPts(i + 1) = Me.PointToScreen(oPts(i + 1))
        ControlPaint.DrawReversibleLine(oOldPts(i), oOldPts(i + 1), objColor)
      Next
      Me.Dirty = True
    End Sub

    ''' <summary>
    ''' Draw a series of erasable lines
    ''' </summary>
    ''' <param name="oPts"></param>
    ''' <remarks></remarks>
    Public Sub DrawErasableLines(ByVal oPts() As PointF)
      'Draw eraseble lines.
      'Pts are in client coordinates
      'static old Pts are in screen coordinates!
      Static oOldPts() As Point

      'Take a middle of the road ColorUtil
      Dim objColor As New Color, i As Integer
      objColor = Color.Gray

      'Avoid trying to erase a rectangle if canvas has just been redrawn!
      If Me.Dirty And Not oOldPts Is Nothing Then
        For i = 0 To oOldPts.Length - 2
          ControlPaint.DrawReversibleLine(oOldPts(i), oOldPts(i + 1), objColor)
        Next
      End If

      'Convert to screen coordinates
      ReDim oOldPts(oPts.Length - 1)
      For i = 0 To oPts.Length - 2
        oOldPts(i) = Me.PointToScreen(Point.Round(oPts(i)))
        oOldPts(i + 1) = Me.PointToScreen(Point.Round(oPts(i + 1)))
        ControlPaint.DrawReversibleLine(oOldPts(i), oOldPts(i + 1), objColor)
      Next
      Me.Dirty = True
    End Sub

    Private Sub DrawInteractiveCreationErasableLines(ByVal clientPt As Point)
      Me.Cursor = Me.cursorDraw
      Dim ann As Annotation = Me._annotation
      If ann.Visible And ann.LineWidth > 0 And ann.Count > 0 Then
        'Some points have already been created, draw an erasable line from last to current 
        'location AND the first point if it is a closed annotation. Annotations are in image coordinates! 
        'If the line is wider than 1 pixel we actually have to draw more lines as the .NET method for
        'drawing reversible lines only draws lines of 1 pixel wide.

        'COMMENT: as we are not using the graphics object with included scaling we need to multiply the drawunit with the zoom for XOR drawing ...
        Dim count As Integer = ann.Count
        Dim linePen As Pen = Me.Annotations.DrawingInfo.GetPen(_annotation.LineWidth * Me.Annotations.DrawingInfo.Unit * Me.DisplayZoom, _annotation.Color)
        If linePen.Width < 1.5 Then
          If _annotation.Closed And count > 1 Then
            Dim oPts(2) As Point
            oPts(0) = ImageToClientCoordinates(ann.Point(count - 1), _displayZoom, _displayOffset)
            oPts(1) = clientPt
            oPts(2) = ImageToClientCoordinates(ann.Point(0), _displayZoom, _displayOffset)
            Me.DrawErasableLines(oPts)
          Else
            Me.DrawErasableLine(ImageToClientCoordinates(ann.Point(count - 1), _displayZoom, _displayOffset), clientPt)
          End If
        Else
          'We use the graphicspath widen method to compute the set of lines that will simulate the wider reversible lines ...
          'A bit odd, but implementing this ourselves is not trivial (line cappings, self-intersections, etc. ...)
          Dim oPts(1) As Point
          oPts(0) = ImageToClientCoordinates(ann.Point(count - 1), _displayZoom, _displayOffset)
          oPts(1) = clientPt
          If _annotation.Closed And count > 1 Then
            ReDim Preserve oPts(2)
            oPts(2) = ImageToClientCoordinates(ann.Point(0), _displayZoom, _displayOffset)
          End If

          'LOL: let graphicspath do the work!!!!
          'Sigh: those routines choke if the points are equal...
          If count > 1 Or count = 1 And Not oPts(0).Equals(oPts(1)) Then
            Dim gp As New GraphicsPath()
            gp.AddLines(oPts)

            'This sometimes causes an out of memory error
            Try
              gp.Widen(linePen)
            Catch ex As Exception

            End Try

            DrawErasableLines(gp.PathPoints)
            gp.Dispose()
          End If
        End If
        linePen.Dispose()
      End If
    End Sub

    Private Sub CreateOptimizedBitmap()
      If _optimizeBitmapDisplay Then
        If Not _bitmap Is Nothing Then
          If Not _optimizedBitmap Is Nothing Then
            _optimizedBitmap.Dispose()
          End If
          Debug.WriteLine("Creating 32bppPARGB bitmap for faster display", "CreateOptimizedBitmap")
          _optimizedBitmap = New Bitmap(_bitmap.Width, _bitmap.Height, Imaging.PixelFormat.Format32bppPArgb)
          Dim g As Graphics = Graphics.FromImage(_optimizedBitmap)
          g.DrawImage(_bitmap, 0, 0, _bitmap.Width, _bitmap.Height)
          g.Dispose()
        Else
          _optimizedBitmap = Nothing
        End If
      Else
        _optimizedBitmap = Nothing
      End If
    End Sub
#End Region

#Region "Cut and paste"
    Public Sub Cut()

    End Sub

    ''' <summary>
    ''' Copy the selected item to the clipboard as an SVG string
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub Copy()
      If Not Me.SelectedItem Is Nothing Then
        If TypeOf Me.SelectedItem Is Annotation Then
          Dim ann As Annotation = CType(Me.SelectedItem, Annotation)
          Dim sb As New System.Text.StringBuilder
          Dim SVG As Xml.XmlWriter = Xml.XmlWriter.Create(sb)
          ann.ToXML(SVG)
          SVG.Close()
          Clipboard.SetText(sb.ToString)
          Debug.WriteLine("Copied selected annotation " & ann.Key & " to clipboard")
        ElseIf TypeOf Me.SelectedItem Is AnnotationContainer Then
          Dim annContainer As AnnotationContainer = CType(Me.SelectedItem, AnnotationContainer)
          Dim sb As New System.Text.StringBuilder
          Dim SVG As Xml.XmlWriter = Xml.XmlWriter.Create(sb)
          annContainer.ToXML(SVG)
          Clipboard.SetText(sb.ToString)
          Debug.WriteLine("Copied selected annotation container  " & annContainer.Key & " to clipboard")
        End If
      End If
    End Sub

    ''' <summary>
    ''' Paste object from clipboard and select it
    ''' </summary>
    ''' <remarks></remarks>
    Public Function Paste() As Boolean
      If Clipboard.ContainsText Then
        Try
          Dim xml As String = Clipboard.GetText
          Dim xmldoc As New Xml.XmlDocument()
          xmldoc.LoadXml(xml)

          Dim gNode As Xml.XmlNode = xmldoc.SelectSingleNode("/g")
          Dim g2Node As Xml.XmlNode = gNode.SelectSingleNode("g")

          Dim annContainer As AnnotationContainer
          If g2Node Is Nothing Then
            'Annotation
            Dim ann As Annotation = Annotation.FromXML(gNode)
            annContainer = New AnnotationContainer()
            annContainer.Color = ann.Color
            annContainer.AddWithSequentialKey("Annotation ", ann)
            Me.Annotations.AddWithSequentialKey("Container ", annContainer)
            ann.Selected = True
          Else
            annContainer = AnnotationContainer.FromXML(gNode)
            Me.Annotations.AddWithSequentialKey("Container ", annContainer)
            annContainer.Selected = True
          End If
          Debug.WriteLine("Pasted annotation container" & annContainer.Key & " from clipboard")
          Return True
        Catch ex As Exception
          Return False
          'MessageBox.Show("Clipboard data has wrong format, make sure you cut or copy from sRGBViewer or a compatible application", "Incompatible clipboard data", MessageBoxButtons.OK, MessageBoxIcon.Error)
        End Try
      End If
    End Function

#End Region

#Region "File and HTTP IO"

    ''' <summary>
    ''' Load the main bitmap from the given filenames, and load annotations from corresponding filename.
    ''' </summary>
    ''' <param name="filename"></param>
    ''' <remarks>Even if the annotations do not exist, the annotations are cleared 
    ''' and the file URI of the annotations will be set appropriately
    ''' so that eveything will work properly when using <b>Save</b>. For more control use the other IO methods.
    ''' </remarks>
    Public Overloads Sub Load(ByVal filename As String)
      Me.Updating = True
      Me.LoadBitmap(New Uri(filename))
      Dim strSVGFilename As String = System.IO.Path.ChangeExtension(filename, ".svg")
      Me.LoadAnnotations(New Uri(strSVGFilename))
      Me.Updating = False
    End Sub

    ''' <summary>
    ''' Save bitmap and annotations to their respective URI's, if they were modified. 
    ''' </summary>
    ''' <remarks>
    ''' </remarks>
    Public Overloads Sub Save()
      If _bitmapSaved = False Then
        SaveBitmap()
      End If

      If _annotationsSaved = False Then
        SaveAnnotations()
      End If
    End Sub

    ''' <summary>
    ''' Save bitmap and annotations to given uri.
    ''' </summary>
    ''' <param name="imageUri"></param>
    ''' <remarks>
    ''' </remarks>
    Public Overloads Sub Save(ByVal imageUri As Uri, ByVal annotationsUri As Uri)
      SaveBitmap(imageUri)
      SaveAnnotations(annotationsUri)
    End Sub

    Private Overloads Sub SaveBitmap(ByVal im As Bitmap, ByVal oUri As Uri)
      Debug.WriteLine("Saving/Streaming image to " & oUri.ToString, "AnnotatedImage.SaveBitmap")

      If oUri.IsFile Then
        'Bitmap.save method sucks, it uses some internal flag of the bitmap to determine the 
        'filetype to use based on the load type, and disregards the file extension!!!!!!
        Dim strFilename As String = oUri.LocalPath
        Dim strExtension As String = System.IO.Path.GetExtension(strFilename)
        im.Save(strFilename, IOUtil.GetImageFileType(strFilename))
      Else
        'We are assuming that the URI can be used to stream image back to web server
        Dim request As HttpWebRequest = IOUtil.GetHTTPUploadRequest(oUri, Me.URISessionID)
        Dim requestStream As IO.Stream = request.GetRequestStream
        Dim imageStream As New IO.MemoryStream

        'Format does not matter as we use the extension in the path in the querystring to determine the image format ....
        'im.Save(imageStream, Imaging.ImageFormat.Png)

        'We try to use the image format. Stream as png if in doubt!
        im.Save(imageStream, im.RawFormat)
        Dim buffer() As Byte = imageStream.ToArray()
        imageStream.Close()

        requestStream.Write(buffer, 0, CInt(buffer.Length))
        requestStream.Close()
        request.GetResponse()
      End If
    End Sub

    Public Overloads Sub SaveBitmap(ByVal oUri As Uri)
      Debug.WriteLine("Saving control bitmap", "AnnotatedImage.SaveBitmap")
      SaveBitmap(_bitmap, oUri)
      'If the orignal image is an http URI we do not update the _bitmapURI property!!!!
      'This allows the user to save a local copy without losing the link to the original URI
      If _bitmapURI.IsFile Then _bitmapURI = oUri
    End Sub

    Public Overloads Sub SaveBitmap()
      SaveBitmap(_bitmapURI)
      _bitmapSaved = True
    End Sub

    Public Sub SaveRenderedBitmap(ByVal oUri As Uri)
      Debug.WriteLine("Saving rendered bitmap", "AnnotatedImage.SaveBitmap")
      SaveBitmap(Me.GetRenderedBitmap, oUri)
    End Sub

    Private Function CreateBitmapFromUri(ByVal oURI As Uri) As Bitmap
      'Generic open of a bitmap. If bitmap is not a 24-bit one, transform it to 24-bit
      'This destroys the Exif tags, which is not a bad things as these seem to 
      'take priority on the standard file information in some case (i.e. resolution).
      'Allow URI download
      Debug.WriteLine("Loading bitmap from " & oURI.ToString, "AnnotatedImage.CreateBitmapFromUri")
      Dim objMemoryBitmap As Bitmap
      Try
        If oURI.IsFile Then
          objMemoryBitmap = CType(Bitmap.FromFile(oURI.LocalPath), Bitmap)
        Else
          'Create a webrequest and stream from resource
          'We try to use the sessionID if present, allowing us to connect
          'to a certain session on a webserver!
          objMemoryBitmap = New Bitmap(IOUtil.GetHTTPDownloadStream(oURI, Me.URISessionID))
        End If

        Return objMemoryBitmap
      Catch ex As Exception
        Debug.WriteLine("Failed to load image from URI " & oURI.ToString & " (" & ex.Message & ")", "AnnotatedImage.LoadBitmap")
        Return Nothing
      End Try
    End Function

    ''' <summary>
    ''' Load a bitmap into the control. 
    ''' </summary>
    ''' <param name="oURI"></param>
    Public Overloads Sub LoadBitmap(ByVal oURI As Uri)
      Debug.WriteLine("Loading bitmap", "AnnotatedImage.LoadBitmap")
      Dim objMemoryBitmap As Bitmap = CreateBitmapFromUri(oURI)

      'Set bitmap
      If Not objMemoryBitmap Is Nothing Then
        Me.SetBitmap(objMemoryBitmap, True)
        _bitmapURI = oURI
        _bitmapSaved = True
      End If
    End Sub

    Public Overloads Sub LoadMask(ByVal oURI As Uri, ByVal key As String)
      Debug.WriteLine("Loading mask bitmap with key " & key, "AnnotatedImage.LoadBitmap")
      Dim oMemoryBitmap As Bitmap = CreateBitmapFromUri(oURI)

      'Set bitmap
      _masks.Add(key, New Mask(oMemoryBitmap))
    End Sub

    Public Overloads Sub SaveMask(ByVal oURI As Uri, ByVal key As String)
      Debug.WriteLine("Saving mask with key " & key, "AnnotatedImage.SaveMask")
      SaveBitmap(_masks.Item(key).Bitmap, oURI)
    End Sub

    ''' <summary>
    ''' Load the annotations from the given Uri. Sets the annotationURI property of the annotatedImage.
    ''' </summary>
    ''' <param name="oUri"></param>
    ''' <remarks>Even if no annotations actually exist, the URI will be set if it is empty. This
    ''' allows settings a non-existing URI for saving the annotations later.
    ''' </remarks>
    Public Sub LoadAnnotations(ByVal oUri As Uri)
      'Avoid extra redraw operations!
      Debug.WriteLine("Loading annotations from " & oUri.ToString, "Annotations.LoadAnnotations")

      Me.Updating = True
      Me.Annotations.Load(oUri, Me.URISessionID)
      _annotationsURI = oUri
      _annotationsSaved = True
      Me.Updating = False
    End Sub

    Public Overloads Sub SaveAnnotations()
      If Me.AnnotationsURI Is Nothing Then Exit Sub
      Me.Annotations.Save(Me.AnnotationsURI, Me.URISessionID)
      _annotationsSaved = True
    End Sub

    Public Overloads Sub SaveAnnotations(ByVal oUri As Uri)
      If Me.AnnotationsURI Is Nothing Then Exit Sub
      Me.Annotations.Save(oUri, Me.URISessionID)
      _annotationsSaved = True
    End Sub

#End Region

#Region "Interactive creation"

    ''' <summary>
    ''' Start interactive creation of an annotation.
    ''' </summary>
    ''' <param name="ann"></param>
    ''' <remarks>The annotation must have been added to an annotationcontainer!
    ''' </remarks>
    Public Sub StartInteractiveAnnotationCreation(ByVal ann As Annotation)
      ' Annotation MUST have been added to an AnnotationContainer!!!!
      Debug.WriteLine("Creating annotation with key " & ann.Key & " interactively", "Annotations.StartInteractiveAnnotationCreation")
      If _creatingAnnotation = True Then
        MessageBox.Show("You cannot create 2 annotations at the same time!", "Create Annotation", MessageBoxButtons.OK, MessageBoxIcon.Error)
      Else
        Dim annContainer As AnnotationContainer = ann.Parent
        If annContainer Is Nothing Then
          'Should raise an exception ...
          Throw New System.ArgumentException("Interactive annotation must be added to AnnotationContainer before calling StartInteractiveAnnotationCreation")
        Else
          'Private properties for creation
          _creatingAnnotation = True
          _annotation = ann

          RaiseEvent AnnotationCreation(ann, AnnotationCreationEventArgs.Start)
        End If
      End If
    End Sub

    Private Function CanOrMustEndInteractiveAnnotationCreation(ByVal bRequestEnd As Boolean) As Boolean
      'Check whether we can or must end the annotation creation.
      'Return true when we request and we must not enter a fixed number of points
      'Returns true when the required number of points is reached regardless of the request
      'Returns false else
      Dim bDoneCreating As Boolean = False
      Debug.WriteLine("Checking for exit of annotation creation", "Annotations.EndInteractiveAnnotationCreation")

      If _annotation.MaximumNumberOfPointsOnCreation > -1 Then
        'Automatic exit is on when the number of pts is reached, and NOT before
        If bRequestEnd Then
          If _annotation.MaximumNumberOfPointsOnCreation <> _annotation.Count Then
            MessageBox.Show("You still have to create " & _annotation.MaximumNumberOfPointsOnCreation - _annotation.Count & " point before exitting annotation creation!", _
                            "Annotation creation", MessageBoxButtons.OK, MessageBoxIcon.Warning)
          Else
            bDoneCreating = True
          End If
        Else
          If _annotation.MaximumNumberOfPointsOnCreation = _annotation.Count Then bDoneCreating = True
        End If
      Else
        If bRequestEnd Then
          bDoneCreating = True
        End If
      End If
      Return bDoneCreating
    End Function

    ''' <summary>
    ''' End interactive creation.
    ''' </summary>
    ''' <remarks>
    ''' </remarks>
    Public Overloads Sub EndInteractiveAnnotationCreation()
      EndInteractiveAnnotationCreation(False)
    End Sub

    ''' <summary>
    ''' End interactive creation of annotation.
    ''' </summary>
    ''' <param name="redo">If redo is true interactive creation of a new annotation similar to the one just created starts immediately</param>
    ''' <remarks>
    ''' </remarks>
    Private Overloads Sub EndInteractiveAnnotationCreation(ByVal redo As Boolean)
      Debug.WriteLine("Exit of annotation creation", "Annotations.EndInteractiveAnnotationCreation")

      'Remove any duplicate points or points too close to each other!
      _annotation.Quantize(Me.CreatedAnnotationMinimalPointDistance, 0)

      'Raise event and stop. Do not set the _annotationBeincreated to nothing after raising the
      'as this would prevent restarting creation ...
      _creatingAnnotation = False
      RaiseEvent AnnotationCreation(Me, AnnotationCreationEventArgs.Ended)

      'Final, full image redraw cleans all up.
      Me.Draw()

      'Start new creation if required. Use a simple sequential key of format NAME_NR
      If redo Then
        Dim ann As New Annotation()
        _annotation.CopyPropertiesTo(ann)
        Dim key As String = _annotation.Key
        Dim lastSpace As Integer = key.LastIndexOf(" ")
        If lastSpace > -1 Then
          If IsNumeric(key.Substring(lastSpace + 1)) Then
            key = key.Substring(0, lastSpace) & " " & CStr(Integer.Parse(key.Substring(lastSpace + 1)) + 1)
          Else
            key = key & " 1"
          End If
        Else
          key = key & " 1"

        End If
        _annotation.Parent.Add(key, ann)

        StartInteractiveAnnotationCreation(ann)
      End If
    End Sub

    ''' <summary>
    ''' Cancel the interactive creation of an annotation.
    ''' </summary>
    ''' <remarks>Annotation is not only cleared of any points, but also removed from parent container if requested
    ''' </remarks>
    Public Sub CancelInteractiveAnnotationCreation()
      If _creatingAnnotation Then
        Me.Updating = True
        _annotation.Clear()
        _annotation.Parent.Remove(_annotation)
        _creatingAnnotation = False
        _annotation = Nothing
        Me.Updating = False

        RaiseEvent AnnotationCreation(Me, AnnotationCreationEventArgs.Canceled)
      End If
    End Sub

    Private Sub RemoveLastAnnotationCreationPoint()
      If _annotation.Count > 0 Then
        _annotation.RemoveAt(_annotation.Count - 1)
      End If
    End Sub

    Private Sub AddAnnotationCreationPoint(ByVal objClientPt As Point, _
                                           ByVal bRequestEnd As Boolean)
      'Add an annotation point and redraw appropriate area
      'Adding is not performed if the new point is equal to the last one!
      Dim objImagePt As PointF = ClientToImageCoordinates(objClientPt, _displayZoom, _displayOffset)

      If _annotation.Count > 0 Then
        If objImagePt.Equals(_annotation.Point(_annotation.Count - 1)) Then
          Debug.WriteLine("Annotation point at " & objImagePt.X & ", " & objImagePt.Y & " is equal to last, NOT added", _
                   "Annotations.AddAnnotationCreationPoint")
          Exit Sub
        End If
      End If

      _annotation.Add(objImagePt)
      Debug.WriteLine("Adding annotation point at " & objImagePt.X & ", " & objImagePt.Y, _
                      "Annotations.AddAnnotationCreationPoint")

      If CanOrMustEndInteractiveAnnotationCreation(bRequestEnd) Then
        EndInteractiveAnnotationCreation()
      End If
    End Sub
#End Region

#Region "GUI"
    ''' <summary>
    ''' Show a dialog window allowing setting of annotation properties
    ''' </summary>
    ''' <param name="ann"></param>
    ''' <remarks>
    ''' </remarks>
    Public Sub SetAnnotationProperties(ByVal ann As Annotation)
      Dim oAnnProps As New AnnotationProperties
      oAnnProps.SetDialogProperties(ann)

      If oAnnProps.ShowDialog() = System.Windows.Forms.DialogResult.OK Then
        oAnnProps.SetAnnotationProperties(ann)
        Me.Draw()
      End If
    End Sub

    ''' <summary>
    ''' Show a dialog window allowing setting of annotation container properties
    ''' </summary>
    ''' <param name="ann"></param>
    ''' <remarks>
    ''' </remarks>
    Public Sub SetAnnotationContainerProperties(ByVal ann As AnnotationContainer)
      Dim oAnnConProps As New AnnotationContainerProperties
      oAnnConProps.SetDialogProperties(ann)

      If oAnnConProps.ShowDialog() = DialogResult.OK Then
        oAnnConProps.SetAnnotationContainerProperties(ann)
        Me.Draw()
      End If
    End Sub

    ''' <summary>
    ''' Create a new annotation container and show the dialog that allows the user 
    ''' to set its properties
    ''' </summary>
    ''' <remarks>
    ''' </remarks>
    Public Sub CreateNewAnnotationContainer()
      Dim oAnnConProps As New AnnotationContainerProperties
      oAnnConProps.SetDialogNewProperties()
      If oAnnConProps.ShowDialog() = DialogResult.OK Then
        oAnnConProps.SetAnnotationContainerProperties(Me.Annotations)
        Me.Draw()
      End If
    End Sub

    ''' <summary>
    ''' Create an new annotation in a container, and show the dialog that allows setting its properties. 
    ''' Start interactive creation after that.
    ''' </summary>
    ''' <param name="container "></param>
    ''' <remarks>
    ''' </remarks>
    Public Sub CreateNewAnnotation(ByVal container As AnnotationContainer)
      Dim oAnnProps As New AnnotationProperties
      oAnnProps.SetDialogNewProperties(container)

      If oAnnProps.ShowDialog() = DialogResult.OK Then
        'Do interactive creation
        Me.StartInteractiveAnnotationCreation(oAnnProps.SetAnnotationProperties(container))
        Me.Draw()
      End If
    End Sub

    ''' <summary>
    ''' Start creation of a new annotation in the 'default' container.
    ''' </summary>
    ''' <remarks>
    ''' </remarks>
    Friend Sub CreateNewAnnotationInDefaultContainer()
      'Find out if the container 'Default' already exists ...
      Dim oAnnContainer As AnnotationContainer
      If _annotations.ContainsKey("Default") Then
        oAnnContainer = _annotations("Default")
      Else
        oAnnContainer = New AnnotationContainer
        oAnnContainer.Color = Color.FromArgb(64, Color.Red)
        _annotations.Add("Default", oAnnContainer)
      End If
      CreateNewAnnotation(oAnnContainer)
    End Sub

    ''' <summary>
    ''' Confirm the deletion of a certain annotation object (annotation or container)
    ''' </summary>
    ''' <param name="oForm"></param>
    ''' <param name="oAnnObj"></param>
    ''' <remarks>
    ''' </remarks>
    Public Sub ConfirmDeleteAnnotationObject(ByVal oForm As Form, ByVal oAnnObj As Object)
      If TypeOf oAnnObj Is Annotation Then
        Me.ConfirmDeleteAnnotation(oForm, CType(oAnnObj, Annotation))
      ElseIf TypeOf oAnnObj Is AnnotationContainer Then
        Me.ConfirmDeleteAnnotationContainer(oForm, CType(oAnnObj, AnnotationContainer))
      End If
    End Sub

    ''' <summary>
    ''' Confirm the deletion of an annotation
    ''' </summary>
    ''' <param name="oForm"></param>
    ''' <param name="oAnn"></param>
    ''' <remarks>
    ''' </remarks>
    Public Sub ConfirmDeleteAnnotation(ByVal oForm As Form, ByVal oAnn As Annotation)
      'Allow delete if clicked, after confirmation!
      If CIPF.SkippableMessage.Show(oForm, "ConfirmDeleteAnnotation", "Deleting annotation", "Are you sure you want to delete the selected annotation?", True, True) = DialogResult.OK Then
        Debug.WriteLine("Removing annotation with key " & oAnn.Key, "menuConfirmDeleteAnnotation_Click")
        oAnn.Parent.Remove(oAnn)
      End If
    End Sub

    ''' <summary>
    ''' Confirm the deletion of an annotation point
    ''' </summary>
    ''' <param name="oForm"></param>
    ''' <param name="oAnn"></param>
    ''' <remarks>
    ''' </remarks>
    Public Sub ConfirmDeleteAnnotationPoint(ByVal oForm As Form, ByVal oAnn As Annotation, ByVal index As Integer)
      'Allow delete if clicked, after confirmation!
      If CIPF.SkippableMessage.Show(oForm, "ConfirmDeleteAnnotationPoint", "Deleting annotation point", "Are you sure you want to delete the selected annotation point?", True, True) = DialogResult.OK Then
        Debug.WriteLine("Removing annotation with key " & oAnn.Key, "menuConfirmDeleteAnnotation_Click")
        oAnn.RemoveAt(index)
      End If
    End Sub

    ''' <summary>
    ''' Confirm the deletion of an annotation container
    ''' </summary>
    ''' <param name="oForm"></param>
    ''' <param name="oAnnContainer"></param>
    ''' <remarks>
    ''' </remarks>
    Public Sub ConfirmDeleteAnnotationContainer(ByVal oForm As Form, ByVal oAnnContainer As AnnotationContainer)
      If CIPF.SkippableMessage.Show(oForm, "ConfirmDeleteAnnotationContainer", "Deleting annotation container", "Are you sure you want to delete the annotation container " & oAnnContainer.Key & "?", True, True) = DialogResult.OK Then
        _annotations.Remove(oAnnContainer.Key)
      End If
    End Sub

    ''' <summary>
    ''' Confirm the deletion of all annotation containers
    ''' </summary>
    ''' <param name="oForm"></param>
    ''' <remarks></remarks>
    Public Sub ConfirmDeleteAllAnnotationContainers(ByVal oForm As Form)
      If CIPF.SkippableMessage.Show(oForm, "ConfirmDeleteAnnotationContainer", "Deleting annotation containers", "Are you sure you want to delete all the annotation containers?", True, True) = DialogResult.OK Then
        _annotations.Clear()
      End If
    End Sub

    ''' <summary>
    ''' Confirm the deletion of a mask
    ''' </summary>
    ''' <param name="oForm"></param>
    ''' <param name="mask"></param>
    ''' <remarks></remarks>
    Public Sub ConfirmDeleteMask(ByVal oForm As Form, ByVal mask As Mask)
      'Allow delete if clicked, after confirmation!
      If CIPF.SkippableMessage.Show(oForm, "ConfirmDeleteMask", "Deleting mask", "Are you sure you want to delete the selected mask?", True, True) = DialogResult.OK Then
        Me.Masks.Remove(mask)
      End If
    End Sub

    ''' <summary>
    ''' Confirm the deletion of all masks
    ''' </summary>
    ''' <param name="oForm"></param>
    ''' <remarks></remarks>
    Public Sub ConfirmDeleteMasks(ByVal oForm As Form)
      'Allow delete if clicked, after confirmation!
      If CIPF.SkippableMessage.Show(oForm, "ConfirmDeleteMasks", "Deleting masks", "Are you sure you want to delete all mask?", True, True) = DialogResult.OK Then
        Me.Masks.Clear()
      End If
    End Sub

    Friend Function DisplayImageBiggerThanControl() As Boolean
      Dim displaySize As Size = Me.DisplaySize
      If Me.MaximumSize.Width < displaySize.Width OrElse Me.MaximumSize.Height < displaySize.Height Then
        Return True
      Else
        Return False
      End If
    End Function

    Private Function ComputeInitialDisplayZoom() As Single
      'Compute the best startDisplayZoom
      If Not _bitmap Is Nothing Then
        Dim sHorDisplayZoom As Single = MaximumSize.Height / CSng(_bitmap.Height())
        Dim sVertDisplayZoom As Single = MaximumSize.Width / CSng(_bitmap.Width())
        Return Math.Min(1, Math.Min(sHorDisplayZoom, sVertDisplayZoom))
      Else
        Return 1
      End If
    End Function

    Private Sub SizeControl(ByVal controlSize As Size)
      'We take care of sizing ourselves, based on the DisplayZoom
      'We cannot exceed the screensize MaximumSize
      'Setting size in one call causes less events in parent!
      'We must be sure a resize event gets triggered because that will cause the 
      'redraw when loading a new image!
      Me.Size = controlSize
      Debug.WriteLine("Control size set to " & Me.Size.ToString(), "AnnotatedImage.SizeControl")
      Me.Dirty = True
      RefreshTopForm()
    End Sub

    Private Sub SizeControl()
      SizeControl(ComputeControlSize())
    End Sub

    Private Function ComputeControlSize() As Size
      Dim controlSize As New Size(799, 599)
      If Not _bitmap Is Nothing Then
        'Compute size of control
        Dim iImageWidth As Integer = CInt(Math.Round(_bitmap.Width() * Me._displayZoom))
        Dim iImageHeight As Integer = CInt(Math.Round(_bitmap.Height() * Me._displayZoom))

        If iImageWidth > Me.MaximumSize.Width Then
          controlSize.Width = Me.MaximumSize.Width
        Else
          controlSize.Width = iImageWidth
        End If

        If iImageHeight > Me.MaximumSize.Height Then
          controlSize.Height = Me.MaximumSize.Height
        Else
          controlSize.Height = iImageHeight
        End If
      End If
      Return controlSize
    End Function

    Private Sub RefreshTopForm()
      'Redraw the whole shebang ...
      'This avoids having to call refresh in higher level controls: all relevant redraws for image and annotations 
      'is handled here!
      Dim oTop As Windows.Forms.Control = Me
      Do While Not oTop.Parent Is Nothing
        oTop = oTop.Parent
      Loop
      Try
        'This sometimes gives an error, BUT WHY???????
        oTop.Refresh()
      Catch ex As Exception
        Debug.WriteLine("Error refreshing topform!!!!!")
      End Try
    End Sub

    Private Sub CheckAndAdjustBitmapRectangle(ByRef objRect As Rectangle)
      If objRect.X < 0 Then
        objRect.Width += objRect.X
        objRect.X = 0
      End If
      If objRect.Y < 0 Then
        objRect.Height += objRect.Y
        objRect.Y = 0
      End If

      Dim fZoom As Single = Me.DisplayZoom
      Dim objBitmap As Bitmap = Me.Bitmap
      If objRect.Bottom >= (objBitmap.Height * fZoom) Then
        objRect.Height = CInt(Math.Round((objBitmap.Height - objRect.Y - 1) * fZoom))
      End If

      If (objRect.Right >= (objBitmap.Width * fZoom)) Then
        objRect.Width = CInt(Math.Round((objBitmap.Width - objRect.X - 1) * fZoom))
      End If
    End Sub
#End Region

#Region "Hit testing"

#End Region

#Region "Misc"

    ''' <summary>
    ''' Check validity of selected item, especially after a delete operation in one of the collections
    ''' </summary>
    ''' <remarks>
    ''' </remarks>
    Private Sub CheckSelectedItem()
      If Not Me.SelectedItem Is Nothing Then
        Dim bSelectedItemExists As Boolean = False
        Dim annContainer As AnnotationContainer
        If TypeOf Me._selectedItem Is Annotation Then
          For Each annContainer In _annotations
            For Each ann As Annotation In annContainer
              If ann.Equals(Me.SelectedItem) Then
                bSelectedItemExists = True
                Exit For
              End If
            Next
            If bSelectedItemExists Then Exit For
          Next
        ElseIf TypeOf Me._selectedItem Is Annotation Then
          For Each annContainer In _annotations.Values
            If annContainer.Equals(Me.SelectedItem) Then
              bSelectedItemExists = True
              Exit For
            End If
          Next
        ElseIf TypeOf Me._selectedItem Is Mask Then
          For Each mask As Mask In Me.Masks
            If mask.Equals(Me.SelectedItem) Then
              bSelectedItemExists = True
              Exit For
            End If
          Next
        End If

        If bSelectedItemExists = False Then Me.SelectedItem.Selected = False
      End If
    End Sub
#End Region
  End Class

End Namespace
